npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@akc42/sqlite-db

v3.0.6

Published

Support for sqlite database in projects using built in node:sqlite but will enhancements

Readme

sqlite-db

Initial import and database opening

sqlite-db is wrapper around the new. node:sqlite package. It provides a range of additional functions, including the ability to run transactions, and more particularly in async mode (see below).

Before using ensure the package it is important that the following variables are set up in your environment (particularly if you are using an import statement at the top of your file.)

SQLITE_DB_DIR Directory (within Container if running in Docker) to find the database files.

SQLITE_DB_NAME Name of the initial database file (which will have '.db' appended to it) to be opened (others can be opened with openDatabase function).

The modules all import thus:-

import mydatabase from '@AKC42/sqlite-db';

this end up with a const variable mydatabase being a class instance of the database on which you can then make calls as described below.

NOTE: we will already have set up to close all connections for this database on process exit, so there is no need to explicity do so yourself.

All modules that do the import like above, even with a different variable name, end up with the same database connection, so can share user defined functions etc.

Database methods.

In this section we are going to assume that you originally did the following

import db from '@AKC42/sqlite-db';

It is likely that one of the first things you will want to do is initialise the database if it empty, run any updates to ensure the database schema is at the correct versions and set up any user defined functions, and if any of these functions are used in GENERATED AS clauses in columns in your ultimate schema you will have to add them and indexes into the database. In addition you will want to remove them on exit. This module has a helper for this as it emits a 'dbclose' event when a database is about to close. So you might want to include something like this in your start up phase.

import db from '@AKC42/sqlite-db';
import { readFileSync } from 'node:fs';
import path from 'node:path';

if (db.isOpen) {
  try {
    const {count} = db.get`SELECT COUNT(*) AS tables FROM pragma_table_list() WHERE schema = 'main' AND name NOT LIKE 'sqlite_%'`
    if (count  === 0) {
      // We are not yet initialised so time to run the initisation script
      const initialiseScriptP =  path.resolve(process.env.SQLITE_DB_INITDIR, `${process.env.SQLITE_DB_NAME}.sql`);
      const initialiseScript = readFileSync(initialiseScriptP,{ encoding: 'utf8' });
      db.transaction((db) => {
        db.exec(initialiseScript);
      });
      // Do we need to do a database upgrade as a result of a recent release?
      const {value: dbVersion} = db.get`SELECT value FROM Settings WHERE name = 'db-version'`;
      if (dbVersion < Number(process.env.DATABASE_VERSION)) {
        const upgradeP =  path.resolve(process.env.SQLITE_DB_INITDIR, `upgrade-${process.env.SQLITE_DB_NAME}.sql`);
        const upgrade = readFileSync(upgradeP,{ encoding: 'utf8' });
        db.transaction((db) => {
          db.exec(upgrade);
          db.run`UPDATE Settings SET value = ${process.env.DATABASE_VERSION} where name = 'db-version`;
        });
      }
      
    }

    //--- add the functions that we need to provide on the database
    db.function('digits',{deterministic: true}, (phone) => {
        if (typeof phone !== 'string') return null;
        return phone.replaceAll(/[^0-9]/g,'');
    });
 


    //------------------
    db.transaction((db) => {
      /*
          we should now add the extra columns and indexes,, but we have to be carefull because a shutdown may have
          failed and we therefore need to check columns for the tables before creating them
        */
       for(const table of ['Enquiry', 'Customer']) {
          const columns = db.all`SELECT name FROM pragma_table_info(${table})`;
          if (!columns.map(c => c.name).includes('digithome')) db.exec(`ALTER TABLE ${table} ADD COLUMN ${'digithome'} TEXT AS (digits(${'telnohome'})) VIRTUAL`);
          if (!ccolumns.map(c => c.name).includes('digitoffice')) db.exec(`ALTER TABLE ${table} ADD COLUMN ${'digitoffice'} TEXT AS (digits(${'telnooffice'})) VIRTUAL`);
          if (!columns.map(c => c.name).includes('digitmobile')) db.exec(`ALTER TABLE ${table} ADD COLUMN ${'digitmobile'} TEXT AS (digits(${'telnomobile'})) VIRTUAL`);
       }
       db.exec(`CREATE INDEX IF NOT EXISTS IX_Enquiry_home ON Enquiry(digithome);
              CREATE INDEX IF NOT EXISTS IX_Enquiry_office ON Enquiry(digitoffice);
              CREATE INDEX IF NOT EXISTS IX_Enquiry_mobile ON Enquiry(digitmobile);
              CREATE INDEX IF NOT EXISTS IX_Customer_home ON Customer(digithome);
              CREATE INDEX IF NOT EXISTS IX_Customer_office ON Customer(digitoffice);
              CREATE INDEX IF NOT EXISTS IX_Customer_mobile ON Customer(digitmobile);`);
    });
    db.once('dbclose', db => {
      db.transaction((db) => {
          /*
            we should now drop the extra columns and indexes,, but we have to be carefull because a startup failre may have
            not have left columns in place.  We need to be sure they are they before we drop them
          */
        db.exec(`DROP INDEX IF EXISTS IX_Enquiry_home;
                  DROP INDEX IF EXISTS IX_Enquiry_office;
                  DROP INDEX IF EXISTS IX_Enquiry_mobile;
                  DROP INDEX IF EXISTS IX_Customer_home;
                  DROP INDEX IF EXISTS IX_Customer_office;
                  DROP INDEX IF EXISTS IX_Customer_mobile;`)

        for(const table of ['Enquiry', 'Customer']) {
            const columns = db.all`SELECT name FROM pragma_table_info(${table})`;
            if (columns.map(c => c.name).includes('digithome')) db.exec(`ALTER TABLE ${table} DROP COLUMN ${'digithome'}`);
            if (columns.map(c => c.name).includes('digitoffice')) db.exec(`ALTER TABLE ${table} DROP COLUMN ${'digitoffice'}`);
            if (columns.map(c => c.name).includes('digitmobile')) db.exec(`ALTER TABLE ${table} DROP COLUMN ${'digitmobile'}`);
        }
          
      });    
    });
  } catch(e) {
    console.log(e.stack)
    db.close(); 
  }
}

This actually shows seven separate methods being used on the database object being returnd

db.isOpen is a read only state set by the module showing the state of the database. It will of course normally be open after initialisation but can indicate to other modules that the database has been closed

db.get takes a template string. This is actually because internally we create a tagStore. We actually support db.all, db.get, db.iterate and db.run

db.exec will take a string of sql commands and execute them. We also support (if you would prefer to use that approach rather than the tag store) db.prepare. This returns the node:sqlite statement and therefore exposes all of that interface.

db.function Allows a function with a callback to be provided to the attached database. The script surrounding that shows that we

db.transaction Performs a transaction with a callback. It is called as shown. Behind the scenes, before the callback a BEGIN TRANSACTION is called. If the callback returns then COMMIT is called. However if the callback throws an error then ROLLBACK is called and the database is closed before the error is repeated.

            In normal circumstances it is important with this transaction that all activity within it is **synchonous** because other work from other modules could get embroiled within this transaction, and also that the callback would appear complete when it is not.  However, this whole module is designed to work in an envirnoment that is primarily asynchonous, and so an alternative is provided, ***db.transactionAsync***.  It is called as follows:-
import { setTimeout } from 'node:timers/promises';

const return = await db.transactionAsync(async (db) => {
  ...
  const result = await setTimeout(100, 'result');
  ...
  return result;
}); 

console.log('Return : ', return);
// Return : result
            Behind the scenes, this function gets a new (dedicated) connection to the database before calling BEGIN TRANSACTION and does a COMMIT or
            ROLLBACK dependant of whether the callback threw and error.  The callback itself is assumed to return a promise which is awaited.  Each
            database on which this `transactionAsync` has its own pool of open connections we can be used to start the transaction and will be returned
            when the transaction ends.  The size of that pool is controlled by by two more environment variables (shown with typical values):-

SQLITE_DB_POOL_MIN_DB=4 Put a connection in the pool instead of closing it if the individual pool size is less than this value

SQLITE_DB_POOL_MAX_DB=100 Stop and wait before opening any more connections across all databases

            Before handing a new connection to the transaction callback, any user defined functions, defined at the time the first connection that is returned was made, are copied over to this new connection.  NOTE: trying to create a new user defined function inside an async transaction will throw an error.

*dbclose Is an event emitted by this module when the database has been closed. This is how we trigger the downgrade action. As well as manually closing the database, the module also detects process.exit and closes all the databases that are open.

db.close Allows you to close the database. What that call doesn't show is that the close function automatically runs db.exec('VACUUM') unless you explicitly tell it not to by passing a true parameter to the call.

            The process exit shutdown tells it to skip Vacuum on close.

There are two additional methods so far not discussed.

db.inTransaction This is set true if a transaction is currently active on the current connection (it does not know the state of other connections).

db.backup This is an async method which backs up the database. It is called with two parameters thus:-

            ```javascript
            await db.backup(backupfilename, authkey);
            ```
            The `backupfilename` should be the name of the backup file to be produced, including directory information.  `authkey` is a special key
            that must be provided which is obtained by calling a separate function (see below).  This is just extra protection against performing it unintentially.

Additional Functions.

If any module wishes to work with another database in parallel, then instead they should import this module like this:-

import {default as mydatabase, openDatabase} from '@AKC42/sqlite-db';

which then also provides the openDatabase function to open additional databases. It is called so:-

const myseconddatabase = await openDatabase(<name>);

where is the equivelent of the SQLITE_DB_NAME environment variable. Note this is not an attachment to the original connection. If you want to attach do

mydatabase.exec(`ATTACH ${path.resolve(process.env.SQLITE_DB_DIR, 'attach.db')}`);

The database returned by openDatabase is in wal mode.

The module extends the Error class with a DatabaseError type. This is exported should it be needed.

As mentioned above, in order to backup a database an authorisation key is needed. This is obtained by calling *backupAuthRequest without any parameters. It returns the key that has to be used with the db.backup method described above.

Lastly debug/logging information is controlled by another function, manage. By default only the point at which a new "max connections" is reached, and when the database is actually closed (not just handed back to the pool) the tagstore at that point. Finally on final shutdown, the maximum size of any tagstore. However, these messages can be switched off, and independantly an output of all the requests with SQL in them can be output. The manage function is called with two parameters. The first is 'debug' or'logger' (case insensitive) saying which type of output to control, and the second parameter is the true or false boolean value which says whether that class of logging should be on or off. Note it dynamically changes, so its perfectly easy to print the first request in the loop but not see any of the others (with debug logging on, execute the call in the loop and immediately turn debug logging off. On exit from the loop turn it back on again).