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 🙏

© 2024 – Pkg Stats / Ryan Hefner

calliope-db

v0.2.3

Published

Simple, generic database interface

Downloads

11

Readme

calliope-js

Simple, generic database interface

Features

  • Callback and promise/async/await support
  • Transaction support
  • Simple, function interface
  • MySQL support
  • Partial Oracle support
  • More databases coming soon!

Configuration

The first step is to configure an object providing access to a connection pool to the database. This object needs to have a single method, getPool, which returns the database-specific connection pool.

In addition, the object can (but may not) have an options property, whose keys can be:

  • log_sql: Boolean; log the SQL to be executed. Default: false.
  • log_parameters: Boolean; log the parameters to the SQL query; this is only valid if log_sql is true. Default: false.

I use a separate module for each database the application requires, containing the connection pool as effectively static so that there is one connection pool for the database in the application.

This object is then wrapped by the appropriate Calliope adaptor, which supplies the DB-specific methods needed by Calliope.

MySQL

Here's an example for MySQL, using the MySQLAdaptor:

const mysql = require('mysql2');
const calliope = require('calliope');

const config = require('./configuration');

const pool = mysql.createPool({
  'connectionLimit': 10,
  'host': config.database.host,
  'port': Number.parseInt(config.database.port),
  'user': config.database.user,
  'namedPlaceholders': true,
  'password': config.database.password,
  'database': config.database.database,
});

module.exports = calliope.MySQLAdaptor({
  getPool: () => pool,
  options: {
    log_sql: true,
    log_parameters: false,
  },
});

Sqlite3

Here is a Sqlite3 example:

const sqlite3 = require('sqlite3');
const calliope = require('calliope');

const config = require('./configuration');

const db = new sqlite3.Database(config.database,
  (err) => {
    return console.error(err.message);
  });

module.exports = calliope.Sqlite3Adaptor({
  getPool: () => db,
  options: {
    log_sql: false,
  }
});

Oracle

And here is an Oracle example:

const oracle = require('oracledb');
const calliope = require('calliope');

const config = require('./configuration');

var pool = null;

oracle.createPool({
  user: config.database.oracle.user,
  password: config.database.oracle.password,
  connectString: config.database.oracle.host +
    ':' + config.database.oracle.port +
    '/' + config.database.oracle.service,
  poolMax: 10,
}, function (error, connection) {
  if (error) {
    console.error(error);
  } else {
    pool = connection;
  }
});

module.exports = calliope.OracleAdaptor({
  getPool: function () {
    if (pool) {
      return pool;
    } else {
      throw 'Oracle: pool not available';
    }
  },
});

Note that Oracle's createPool function is asynchronous and the resulting connection will not be available immediately. (Hence the callback assigning to the pool variable.)

Note Oracle support is not entirely complete as of this version. That should be remedied soon.

Usage

Calliope's base use-case is for static SQL statements (but see Mockingbird-SQL support). Fundamentally, you feed the constructor an array of SQL statements and related information along with the database connection pool object described above, and the resulting object has one methed per SQL statement.

An array of queries

This is a sample list of queries:

const queries = [
  {
    name: 'get_location_by_event',
    sql:  'SELECT location_id FROM event WHERE event_id = :event_id',
  },
  {
    name: 'get_event_notifier',
    sql: `
    SELECT person.person_id
    FROM
      person
      JOIN event on (person.person_id = event.fk_person_id)
    WHERE
      person.assoc-type_id = 6
      AND event.fk_person_id = :event_id
    LIMIT 1`,
  },
];

To use these queries with the MySQL connection pool above, create a Calliope connection object:

const calliope = require('calliope');
const mysqlDb = require('../utility/mysql-db');

const Db = new calliope.Db(queries, mysqlDb);

Then, the SQL queries can be made by executing functions:

Db.get_location_by_event({ event_id: event_id }, function (error, result) {
  // check error and do something useful with result
});

Or, it can be called with async/await:

let results = await Db.get_location_by_event({event_id: event_id});

Transactions start by getting a connection using beginTransaction, and should be committed with commit or rolled-back with rollback:

try {
  let connection = await Db.beginTransaction();
  try {
    let locationResults =
      await Db.get_location_by_event({event_id: event_id}, connection);
    let notifierResults =
      await Db.get_event_notifier({event_id: event_id}, connection);
    // ...
    await Db.commit(connection);
  } catch (error) {
    await Db.rollback(connection);
    throw error; // pass error along after rollback
  }
} catch (error) {
  // do something with error
}

Note that the transaction support requires the use of async/await (or Promise). (The connection and the callback are passed as the second argument for usability.)

One escape mechanism is to make an executeQuery call directly to the underlying database support:

Db.executeQuery(sql, args, callback);

Parameters available to executeQuery are:

  • sql: The SQL statement to be executed,
  • params: the parameters to the query,
  • callback: a callback function which takes error, results parameters, and
  • connection (optional): the connection to use to make the query.

Finally, as another escape mechanism, the database-specific connection pool itself can be accessed:

let pool = Db.getPool();

Details

Query objects

The objects in the array of queries can have the following keys:

  • name: This key is required and supplies the name of the generated method on the Calliope object. This must be a string.
  • sql: This key provides the SQL query to execute. This can be either a string, giveng the SQL, or a function, returning a mockingbird-sql generated-SQL structure. See below.
  • type: This key describes the type of the query: SELECT, INSERT, or UPDATE. The default is "SELECT". See below.
  • table: See INSERT and UPDATE below.
  • columns: See INSERT and UPDATE below.
  • idColumn: See UPDATE below.

Generated functions

The generated methods look like function (values, cc = null).

values are the arguments to the query. These are generally passed to the database driver. For the MySQL (i.e. with namedPlaceholders: true) and Oracle configurations described above, values can be an object with keys matching ':parameters' in the query.

If the sql key in the query object is a function returning a mockingbird-sql structure, the values are passed to the mockingbird-sql function, which converts the structure into a SQL string an the actual arguments.

cc is either

  • null, in which case the query is made normally but the method returns a Promise,
  • a callback function with error and results parameters, in which case the query is made and the output passed to the callback, or
  • a connection, in which case the connection is used to perform the query and a Promise is returned.

Query types

SELECT is the default and operates as described above. Note that the actual SQL can be any of SELECT, INSERT, UPDATE, DELETE, etc.

INSERT is intended to provide an easy, general way to insert a row into the table. In this case, the SQL is generated, and the query object needs two additional keys:

  • table: the name of the table to insert into, and
  • columns: an object mapping column names in the table to one of:
    • a string, which will be used as the parameter in the SQL query, or
    • true, in which case the SQL parameter will be '?'.

The values parameter to the generated method will provide the data to be inserted.

An example:

  {
    name:    'insert_event',
    type:    'INSERT',
    table:   'event',
    columns: {
      'create_time': 'FROM_UNIXTIME(?)',
      'created_by': true,
      'event_details': true,
    },
  },

Assume values is

{
  create_time: Math.floor(Date.now() / 1000),
  created_by: 'ted',
  event_details: 'Something happened',
}

In this case the SQL will be something like:

INSERT INTO event (
  create_time,
  created_by,
  event_details
) VALUES (
  FROM_UNIXTIME(?),
  ?,
  ?
)

UPDATE is intended to provide an easy, general way to update a row in the table. It is very similar to INSERT, but the query object requires an additional key: idColumn. This key supplies the column name used to identify the row to be updated.

Another example:

  {
    name:    'update_EVENT',
    type:    'UPDATE',
    table:   'event',
    columns: {
      'create_time': 'FROM_UNIXTIME(?)',
      'created_by': true,
      'event_details': true,
    },
    idColumn: 'event_id',
  },

Assuming the values are:

{
  event_details: 'Something happened, again',
  create_time: Math.floor(Date.now() / 1000),
  event_id: 12,
}

The generated SQL will be:

UPDATE event SET
  event_details = ?,
  create_time = FROM_UNIXTIME(?),
WHERE
  event_id = ?

TODO

  • Support inserts for Oracle