@photostructure/sqlite
    Preparing search index...

    API reference

    Complete API documentation for @photostructure/sqlite. This package provides 100% compatibility with Node.js's built-in SQLite module.

    The module exports the following items that match node:sqlite:

    import {
    DatabaseSync, // Main database class
    StatementSync, // Prepared statement class
    Session, // Session class for changesets
    backup, // Standalone backup function
    constants, // SQLite constants
    } from "@photostructure/sqlite";

    This package also provides enhancement utilities for (some) better-sqlite3 compatibility:

    import {
    enhance, // Add .pragma() and .transaction() to any compatible database
    isEnhanced, // Type guard to check if database has enhanced methods
    } from "@photostructure/sqlite";
    backup(
    sourceDb: DatabaseSync,
    destination: string | Buffer | URL,
    options?: BackupOptions
    ): Promise<number>

    Standalone function to create a backup of a database.

    Parameters:

    • sourceDb - The database instance to back up
    • destination - Path to the backup file (string, Buffer, or file: URL)
    • options - Optional backup configuration

    Returns: A Promise that resolves to the total number of pages backed up.

    import { DatabaseSync, backup } from "@photostructure/sqlite";

    const db = new DatabaseSync("source.db");

    // Create a backup
    await backup(db, "backup.db");

    // With progress monitoring
    await backup(db, "backup.db", {
    rate: 10,
    progress: ({ totalPages, remainingPages }) => {
    console.log(`${totalPages - remainingPages}/${totalPages} pages copied`);
    },
    });

    These utilities add better-sqlite3-style convenience methods to database instances. They are an extension beyond the node:sqlite API.

    enhance<T extends EnhanceableDatabaseSync>(db: T): EnhancedDatabaseSync<T>
    

    Adds .pragma() and .transaction() methods to any compatible database instance. This enables better-sqlite3-style convenience methods on node:sqlite DatabaseSync instances or any object with compatible exec(), prepare(), and isTransaction properties.

    The enhancement is done by adding methods directly to the instance, not the prototype, so it won't affect other instances or the original class.

    Parameters:

    • db - The database instance to enhance

    Returns: The same instance with .pragma() and .transaction() methods guaranteed.

    import { DatabaseSync, enhance } from "@photostructure/sqlite";

    // Enhance a database to add better-sqlite3-style methods
    const db = enhance(new DatabaseSync(":memory:"));

    // Now you can use .pragma()
    db.pragma("journal_mode = WAL");
    const cacheSize = db.pragma("cache_size", { simple: true });

    // And .transaction()
    const insertMany = db.transaction((items) => {
    const stmt = db.prepare("INSERT INTO data (value) VALUES (?)");
    for (const item of items) stmt.run(item);
    });
    insertMany(["a", "b", "c"]); // All in one transaction

    Note: If the database already has .pragma() and .transaction() methods (e.g., if you call enhance() twice), it returns the database unchanged.

    isEnhanced(db: EnhanceableDatabaseSync): db is EnhancedDatabaseSync
    

    Type guard to check if a database has enhanced methods.

    import { isEnhanced } from "@photostructure/sqlite";

    if (isEnhanced(db)) {
    // TypeScript knows db has .pragma() and .transaction()
    db.pragma("cache_size", { simple: true });
    }

    When a database is enhanced, it gains these methods:

    pragma(source: string, options?: PragmaOptions): unknown
    

    Executes a PRAGMA statement and returns its result.

    Parameters:

    • source - The PRAGMA command (without "PRAGMA" prefix)
    • options - Optional configuration
      • simple - If true, returns the first column of the first row instead of an array of row objects

    Returns: Array of rows, or single value if simple: true.

    // Get all rows
    const result = db.pragma("table_info(users)");
    // [{ cid: 0, name: 'id', type: 'INTEGER', ... }, ...]

    // Get simple value
    const journalMode = db.pragma("journal_mode", { simple: true });
    // 'wal'

    // Set a pragma
    db.pragma("cache_size = -16000"); // 16MB cache
    transaction<F extends (...args: any[]) => any>(fn: F): TransactionFunction<F>
    

    Creates a function that always runs inside a transaction. If an exception is thrown, the transaction is rolled back; otherwise, it's committed.

    Parameters:

    • fn - The function to wrap in a transaction

    Returns: A transaction function with .deferred, .immediate, .exclusive variants.

    const insert = db.prepare("INSERT INTO users (name) VALUES (?)");

    // Create a transaction function
    const insertMany = db.transaction((names) => {
    for (const name of names) {
    insert.run(name);
    }
    });

    // Use it - automatically wrapped in BEGIN/COMMIT
    insertMany(["Alice", "Bob", "Charlie"]);

    // Use different transaction modes
    insertMany.immediate(["Dave"]); // BEGIN IMMEDIATE
    insertMany.exclusive(["Eve"]); // BEGIN EXCLUSIVE
    insertMany.deferred(["Frank"]); // BEGIN DEFERRED (default)

    Nested transactions: When called inside an existing transaction, uses savepoints automatically:

    const outer = db.transaction(() => {
    insert.run("outer");
    try {
    inner(); // Uses SAVEPOINT
    } catch (e) {
    // Inner rolled back, outer continues
    }
    });

    const inner = db.transaction(() => {
    insert.run("inner");
    throw new Error("rollback inner only");
    });

    outer(); // "outer" is committed, "inner" is not

    The main database class for synchronous SQLite operations.

    new DatabaseSync(location: string, options?: DatabaseSyncOptions)
    

    Creates a new database connection.

    Parameters:

    • location - Path to database file. Special values:
      • :memory: - In-memory database
      • "" (empty string) - Temporary on-disk database
      • URI format supported (e.g., file:data.db?mode=ro)
    • options - Optional configuration object

    Options:

    interface DatabaseSyncOptions {
    open?: boolean; // Open database immediately (default: true)
    readOnly?: boolean; // Open in read-only mode (default: false)
    enableForeignKeyConstraints?: boolean; // Enable foreign keys (default: true)
    enableDoubleQuotedStringLiterals?: boolean; // Allow double-quoted strings (default: false)
    allowExtension?: boolean; // Allow loading extensions (default: false)
    timeout?: number; // Busy timeout in ms (default: 0)
    readBigInts?: boolean; // Return BigInt for INTEGER columns (default: false)
    returnArrays?: boolean; // Return rows as arrays instead of objects (default: false)
    allowBareNamedParameters?: boolean; // Allow $name without : prefix (default: true)
    allowUnknownNamedParameters?: boolean; // Allow unbound named parameters (default: false)
    defensive?: boolean; // Enable defensive mode (default: false)
    }

    Examples:

    // Basic usage
    const db = new DatabaseSync("myapp.db");

    // In-memory database
    const memDb = new DatabaseSync(":memory:");

    // Read-only with options
    const readOnlyDb = new DatabaseSync("data.db", {
    readOnly: true,
    timeout: 10000,
    });

    // URI format
    const uriDb = new DatabaseSync("file:data.db?mode=ro&cache=private");
    close(): void
    

    Closes the database connection. All prepared statements are finalized automatically.

    db.close();
    
    [Symbol.dispose](): void
    

    Implements the disposable interface for automatic resource management. Calls close() internally, ignoring any errors during disposal. Implemented natively in C++ for better performance.

    // Automatic cleanup with using statement
    using db = new DatabaseSync("myapp.db");
    // db.close() called automatically when leaving scope

    // Or explicit disposal
    db[Symbol.dispose]();
    exec(sql: string): void
    

    Executes one or more SQL statements. Does not return any results.

    db.exec(`
    CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
    CREATE INDEX idx_name ON users(name);
    `);
    prepare(sql: string, options?: StatementOptions): StatementSync
    

    Prepares a SQL statement for execution.

    Options:

    interface StatementOptions {
    expandedSQL?: boolean; // Include expanded SQL (default: false)
    anonymousParameters?: boolean; // Enable anonymous parameters (default: false)
    // Per-statement overrides (Node.js v25+ feature):
    readBigInts?: boolean; // Override database-level BigInt setting
    returnArrays?: boolean; // Override database-level array return setting
    allowBareNamedParameters?: boolean; // Override bare parameter setting
    allowUnknownNamedParameters?: boolean; // Override unknown parameter setting
    }

    Note: The per-statement override options (readBigInts, returnArrays, allowBareNamedParameters, allowUnknownNamedParameters) are a Node.js v25+ feature. On Node.js v24 and earlier, node:sqlite silently ignores these options. This library implements them for forward compatibility.

    const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
    const stmtWithExpanded = db.prepare("SELECT * FROM users WHERE id = ?", {
    expandedSQL: true,
    });

    // Per-statement option overrides (Node.js v25+)
    const stmtBigInt = db.prepare("SELECT big_id FROM data", { readBigInts: true });
    const stmtArrays = db.prepare("SELECT * FROM users", { returnArrays: true });
    function(name: string, options: FunctionOptions | Function, func?: Function): void
    

    Registers a custom scalar SQL function.

    Options:

    interface FunctionOptions {
    deterministic?: boolean; // Same input always gives same output (default: false)
    directOnly?: boolean; // Cannot be used in triggers/views (default: false)
    useBigIntArguments?: boolean; // Receive BigInt instead of number for INTEGER args (default: false)
    varargs?: boolean; // Accept variable number of arguments (default: false)
    }
    // Simple function
    db.function("double", (x) => x * 2);

    // With options
    db.function(
    "hash",
    {
    deterministic: true,
    directOnly: true,
    },
    (value) => {
    return crypto.createHash("sha256").update(String(value)).digest("hex");
    },
    );

    // Variable arguments
    db.function("concat", { varargs: true }, (...args) => args.join(""));
    aggregate(name: string, options: AggregateOptions): void
    

    Registers a custom aggregate SQL function.

    Options:

    interface AggregateOptions {
    start: any | (() => any); // Initial value or factory function
    step: (accumulator: any, ...values: any[]) => any; // Called for each row
    result?: (accumulator: any) => any; // Final result transformer (optional)
    inverse?: (accumulator: any, ...values: any[]) => any; // For window functions
    deterministic?: boolean; // Same input always gives same output (default: false)
    directOnly?: boolean; // Cannot be used in triggers/views (default: false)
    useBigIntArguments?: boolean; // Receive BigInt for INTEGER args (default: false)
    varargs?: boolean; // Accept variable number of arguments (default: false)
    }
    // Sum aggregate
    db.aggregate("custom_sum", {
    start: 0,
    step: (sum, value) => sum + value,
    result: (sum) => sum,
    });

    // Average aggregate
    db.aggregate("custom_avg", {
    start: { sum: 0, count: 0 },
    step: (acc, value) => {
    acc.sum += value;
    acc.count += 1;
    return acc;
    },
    result: (acc) => acc.sum / acc.count,
    });

    // Window function with inverse
    db.aggregate("moving_sum", {
    start: 0,
    step: (sum, value) => sum + value,
    inverse: (sum, value) => sum - value, // Remove value leaving window
    result: (sum) => sum,
    });
    createSession(options?: SessionOptions): Session
    

    Creates a session for tracking changes.

    Options:

    interface SessionOptions {
    table?: string; // Specific table to track
    db?: string; // Database name (default: 'main')
    }
    const session = db.createSession({ table: "users" });
    // Make changes...
    const changeset = session.changeset();
    session.close();
    applyChangeset(changeset: Buffer, options?: ChangesetApplyOptions): boolean
    

    Applies a changeset to the database. Returns true if successful, false if aborted.

    Options:

    interface ChangesetApplyOptions {
    onConflict?: (conflictType: number) => number; // Returns resolution constant
    filter?: (tableName: string) => boolean; // Filter which tables to apply
    }
    const success = db.applyChangeset(changeset, {
    onConflict: (conflictType) => {
    // conflictType is one of: SQLITE_CHANGESET_DATA, SQLITE_CHANGESET_NOTFOUND,
    // SQLITE_CHANGESET_CONFLICT, SQLITE_CHANGESET_CONSTRAINT, SQLITE_CHANGESET_FOREIGN_KEY
    console.log(`Conflict type: ${conflictType}`);
    return constants.SQLITE_CHANGESET_REPLACE;
    },
    });
    enableLoadExtension(enable: boolean): void
    

    Enables or disables extension loading. Requires allowExtension: true in constructor.

    db.enableLoadExtension(true);
    db.loadExtension("./my-extension.so");
    db.enableLoadExtension(false);
    loadExtension(path: string, entryPoint?: string): void
    

    Loads a SQLite extension.

    db.loadExtension("./extensions/vector.so");
    db.loadExtension("./custom.so", "sqlite3_custom_init");
    open(): void
    

    Opens the database connection. Only needed if open: false was passed to the constructor.

    const db = new DatabaseSync("myapp.db", { open: false });
    // ... configure something ...
    db.open();
    setAuthorizer(callback: AuthorizerCallback | null): void
    

    Sets an authorizer callback to control access to database operations. Pass null to remove the authorizer.

    Callback signature:

    type AuthorizerCallback = (
    actionCode: number,
    arg1: string | null,
    arg2: string | null,
    dbName: string | null,
    triggerOrView: string | null,
    ) => number; // Returns SQLITE_OK, SQLITE_DENY, or SQLITE_IGNORE
    db.setAuthorizer((action, arg1, arg2, dbName, trigger) => {
    if (action === constants.SQLITE_DROP_TABLE) {
    return constants.SQLITE_DENY; // Prevent dropping tables
    }
    return constants.SQLITE_OK;
    });
    enableDefensive(active: boolean): void
    

    Enables or disables defensive mode, which prevents application bugs from corrupting the database.

    db.enableDefensive(true);
    
    createTagStore(maxSize?: number): SQLTagStore
    

    Creates a SQLTagStore for cached prepared statements using tagged template literals.

    const sql = db.createTagStore();
    sql.run`INSERT INTO users VALUES (${id}, ${name})`;
    const user = sql.get`SELECT * FROM users WHERE id = ${id}`;
    readonly isOpen: boolean
    

    Returns true if the database connection is open.

    console.log(db.isOpen); // true
    db.close();
    console.log(db.isOpen); // false
    readonly isTransaction: boolean
    

    Returns true if a transaction is currently active.

    db.exec("BEGIN");
    console.log(db.isTransaction); // true
    db.exec("COMMIT");
    console.log(db.isTransaction); // false
    location(dbName?: string): string | null
    

    Returns the file path of the database, or null for in-memory databases.

    console.log(db.location()); // "myapp.db"
    console.log(db.location("main")); // "myapp.db"

    Represents a prepared SQL statement.

    run(...params: any[]): RunResult
    

    Executes the statement and returns information about changes.

    Returns:

    interface RunResult {
    changes: number; // Number of rows affected
    lastInsertRowid: number | bigint; // Last inserted row ID
    }
    const result = stmt.run("Alice", 30);
    console.log(`Inserted row ${result.lastInsertRowid}`);
    get(...params: any[]): any
    

    Executes the statement and returns the first row.

    const user = stmt.get(1);
    console.log(user); // { id: 1, name: 'Alice', age: 30 }
    all(...params: any[]): any[]
    

    Executes the statement and returns all rows.

    const users = stmt.all();
    console.log(users); // Array of all user objects
    iterate(...params: any[]): Iterator<any>
    

    Executes the statement and returns an iterator for the results.

    for (const row of stmt.iterate()) {
    console.log(row);
    }
    columns(): ColumnInfo[]
    

    Returns metadata about the columns in the result set.

    Returns:

    interface ColumnInfo {
    column: string | null; // Original column name
    database: string | null; // Database name
    name: string; // Column alias or name
    table: string | null; // Table name
    type: string | null; // Declared type
    }
    const stmt = db.prepare("SELECT id, name FROM users");
    console.log(stmt.columns());
    // [
    // { column: 'id', database: 'main', name: 'id', table: 'users', type: 'INTEGER' },
    // { column: 'name', database: 'main', name: 'name', table: 'users', type: 'TEXT' }
    // ]
    setReadBigInts(enabled: boolean): void
    

    Configures whether INTEGER columns are returned as BigInt values.

    stmt.setReadBigInts(true);
    const row = stmt.get(1);
    console.log(typeof row.id); // "bigint"
    setReturnArrays(enabled: boolean): void
    

    Configures whether rows are returned as arrays instead of objects.

    stmt.setReturnArrays(true);
    const row = stmt.get(1);
    console.log(row); // [1, 'Alice', 30] instead of { id: 1, name: 'Alice', age: 30 }
    setAllowBareNamedParameters(enabled: boolean): void
    

    Configures whether named parameters can be used without the : prefix.

    stmt.setAllowBareNamedParameters(true);
    stmt.run({ name: "Alice" }); // Works without :name
    setAllowUnknownNamedParameters(enabled: boolean): void
    

    Configures whether unbound named parameters are allowed (they resolve to NULL).

    stmt.setAllowUnknownNamedParameters(true);
    stmt.run({ name: "Alice" }); // Extra params in object are ignored
    readonly sourceSQL: string
    

    The original SQL text of the statement.

    console.log(stmt.sourceSQL); // "SELECT * FROM users WHERE id = ?"
    
    readonly expandedSQL: string
    

    The SQL with bound parameters expanded (only if expandedSQL: true option was used).

    const stmt = db.prepare("SELECT * FROM users WHERE id = ?", {
    expandedSQL: true,
    });
    stmt.get(42);
    console.log(stmt.expandedSQL); // "SELECT * FROM users WHERE id = 42"
    interface DatabaseSyncOptions {
    open?: boolean; // Open database immediately (default: true)
    readOnly?: boolean; // Open in read-only mode (default: false)
    enableForeignKeyConstraints?: boolean; // Enable foreign keys (default: true)
    enableDoubleQuotedStringLiterals?: boolean; // Allow double-quoted strings (default: false)
    allowExtension?: boolean; // Allow loading extensions (default: false)
    timeout?: number; // Busy timeout in ms (default: 0)
    readBigInts?: boolean; // Return BigInt for INTEGER columns (default: false)
    returnArrays?: boolean; // Return rows as arrays instead of objects (default: false)
    allowBareNamedParameters?: boolean; // Allow $name without : prefix (default: true)
    allowUnknownNamedParameters?: boolean; // Allow unbound named parameters (default: false)
    defensive?: boolean; // Enable defensive mode (default: false)
    }
    interface StatementOptions {
    expandedSQL?: boolean;
    anonymousParameters?: boolean;
    }
    interface FunctionOptions {
    deterministic?: boolean; // Same input always gives same output (default: false)
    directOnly?: boolean; // Cannot be used in triggers/views (default: false)
    useBigIntArguments?: boolean; // Receive BigInt for INTEGER args (default: false)
    varargs?: boolean; // Accept variable number of arguments (default: false)
    }
    interface AggregateOptions {
    start: any | (() => any); // Initial value or factory function
    step: (accumulator: any, ...values: any[]) => any; // Called for each row
    result?: (accumulator: any) => any; // Final result transformer (optional)
    inverse?: (accumulator: any, ...values: any[]) => any; // For window functions
    deterministic?: boolean; // Same input always gives same output (default: false)
    directOnly?: boolean; // Cannot be used in triggers/views (default: false)
    useBigIntArguments?: boolean; // Receive BigInt for INTEGER args (default: false)
    varargs?: boolean; // Accept variable number of arguments (default: false)
    }
    interface BackupOptions {
    source?: string; // Source database name (default: 'main')
    target?: string; // Target database name (default: 'main')
    rate?: number; // Pages per iteration (default: 100)
    progress?: (info: { totalPages: number; remainingPages: number }) => void;
    }
    interface ColumnInfo {
    column: string | null; // Original column name
    database: string | null; // Database name
    name: string; // Column alias or name
    table: string | null; // Table name
    type: string | null; // Declared type
    }
    interface RunResult {
    changes: number | bigint; // Number of rows affected
    lastInsertRowid: number | bigint; // Last inserted row ID
    }
    interface PragmaOptions {
    simple?: boolean; // Return first column of first row instead of array
    }
    interface TransactionFunction<F extends (...args: any[]) => any> {
    (...args: Parameters<F>): ReturnType<F>;
    deferred: (...args: Parameters<F>) => ReturnType<F>;
    immediate: (...args: Parameters<F>) => ReturnType<F>;
    exclusive: (...args: Parameters<F>) => ReturnType<F>;
    }

    Minimal interface for a database that can be enhanced:

    interface EnhanceableDatabaseSync {
    exec(sql: string): void;
    prepare(sql: string): { all(): unknown[] };
    readonly isTransaction: boolean;
    }

    Interface for enhanced database methods:

    interface EnhancedMethods {
    pragma(source: string, options?: PragmaOptions): unknown;
    transaction<F extends (...args: any[]) => any>(fn: F): TransactionFunction<F>;
    }

    A database instance that has been enhanced:

    type EnhancedDatabaseSync<T extends EnhanceableDatabaseSync> = T &
    EnhancedMethods;

    The package exports SQLite constants for use with sessions, changesets, and authorization:

    import { constants } from "@photostructure/sqlite";

    // Conflict resolution constants
    constants.SQLITE_CHANGESET_OMIT;
    constants.SQLITE_CHANGESET_REPLACE;
    constants.SQLITE_CHANGESET_ABORT;

    // Authorization result codes
    constants.SQLITE_OK;
    constants.SQLITE_DENY;
    constants.SQLITE_IGNORE;

    // Authorization action codes
    constants.SQLITE_CREATE_TABLE;
    constants.SQLITE_INSERT;
    constants.SQLITE_SELECT;
    // ... and more

    Note: These categorized type interfaces are an extension provided by @photostructure/sqlite. The node:sqlite module exports only a flat constants object without these type categories.

    This package provides strongly-typed interfaces for different constant categories, enabling better TypeScript type checking and IntelliSense:

    import {
    constants,
    // Type interfaces (not in node:sqlite)
    SqliteConstants,
    SqliteOpenFlags,
    SqliteChangesetResolution,
    SqliteChangesetConflictTypes,
    SqliteAuthorizationResults,
    SqliteAuthorizationActions,
    } from "@photostructure/sqlite";

    Database open flags. These constants are an extension beyond node:sqlite - the node:sqlite module does not export SQLITE_OPEN_* constants.

    Constant Description
    SQLITE_OPEN_READONLY Open database for reading only
    SQLITE_OPEN_READWRITE Open database for reading and writing
    SQLITE_OPEN_CREATE Create database if it doesn't exist
    SQLITE_OPEN_URI Interpret filename as URI
    SQLITE_OPEN_MEMORY Open in-memory database
    SQLITE_OPEN_NOMUTEX Open without mutex
    SQLITE_OPEN_FULLMUTEX Open with full mutex
    SQLITE_OPEN_SHAREDCACHE Enable shared cache mode
    SQLITE_OPEN_PRIVATECACHE Enable private cache mode
    SQLITE_OPEN_WAL Open WAL file
    ... (10 more flags available)

    Return values for applyChangeset() conflict callbacks:

    Constant Description
    SQLITE_CHANGESET_OMIT Skip conflicting changes
    SQLITE_CHANGESET_REPLACE Replace conflicting changes
    SQLITE_CHANGESET_ABORT Abort on conflict

    Conflict type codes passed to applyChangeset() callbacks:

    Constant Description
    SQLITE_CHANGESET_DATA Row exists but values differ
    SQLITE_CHANGESET_NOTFOUND Row not found in target
    SQLITE_CHANGESET_CONFLICT Primary key conflict
    SQLITE_CHANGESET_CONSTRAINT Constraint violation
    SQLITE_CHANGESET_FOREIGN_KEY Foreign key violation

    Return values for setAuthorizer() callbacks:

    Constant Description
    SQLITE_OK Allow the operation
    SQLITE_DENY Deny and abort with error
    SQLITE_IGNORE Silently skip the operation

    Action codes passed to setAuthorizer() callbacks (34 total):

    Constant Description
    SQLITE_CREATE_TABLE Create a new table
    SQLITE_INSERT Insert rows
    SQLITE_SELECT Execute SELECT
    SQLITE_UPDATE Update rows
    SQLITE_DELETE Delete rows
    SQLITE_CREATE_INDEX Create an index
    SQLITE_DROP_TABLE Drop a table
    SQLITE_PRAGMA Execute PRAGMA
    SQLITE_ATTACH Attach a database
    SQLITE_DETACH Detach a database
    ... (24 more action codes)

    The categorized types enable strongly-typed function signatures:

    import {
    constants,
    SqliteChangesetResolution,
    SqliteChangesetConflictTypes,
    } from "@photostructure/sqlite";

    function handleConflict(
    conflictType: keyof SqliteChangesetConflictTypes,
    ): keyof SqliteChangesetResolution {
    if (conflictType === "SQLITE_CHANGESET_DATA") {
    return "SQLITE_CHANGESET_REPLACE";
    }
    return "SQLITE_CHANGESET_OMIT";
    }

    db.applyChangeset(changeset, {
    onConflict: (type) => constants[handleConflict(type)],
    });

    All errors include additional information:

    interface SQLiteError extends Error {
    code: string; // e.g., "SQLITE_CANTOPEN"
    sqliteCode: number; // e.g., 14
    sqliteExtendedCode: number;
    sqliteErrorString: string; // Human-readable description
    systemErrno?: number; // OS error code (when applicable)
    }
    try {
    db.exec("INVALID SQL");
    } catch (error) {
    console.log(error.message); // Full error message
    console.log(error.code); // "SQLITE_ERROR"
    console.log(error.sqliteCode); // 1
    console.log(error.sqliteErrorString); // "SQL logic error"
    }