pg-promise (part 2)

This is the next part of the open-source review of pg-promise[1]. In this part, I want to take a closer look at the overall structure of the project and will also cover some of the larger and major components.

After reading this and part 1, my hope is that you should have enough knowledge to move around the project and understand where to navigate for what. Additionally, you should understand how queries are handled.

Module Diagram of lib/main.js

Following is a high-level "module" diagram of pg-promise's entry point lib/main.js; this shows the different modules of pg-promise. The coloring shows their root module. The purpose of this is to see which modules main directly depends on.

Module Diagram of pg-promise

Note: this is not all the modules within pg-promise.

Main Module

This function invocation mainly parses the option and sets up what becomes the pgp instance for use.

The returned instance is actually an anonymous function referred to as const inst in the implementation; once you invoke the module such as require('pg-promise')({/*opts*/}) you receive the instance held in inst. This is ultimately what is returned as pgp. The object is enriched via npm.utils.addReadProp to have the API as documented.

Although this leaves out a lot of details, the high level flow is depicted in the following:

Flow diagram of the Main module

This takes us to the next module of instance, Database.js which defines the API to run promised based queries against pg-promise.

Database.js

What is exported is a function that accepts config and returns the internal Database object. That is function Database().

module.exports = config => {
    const npmLocal = config.$npm;
    npmLocal.connect = npmLocal.connect || npm.connect(config);
    npmLocal.query = npmLocal.query || npm.query(config);
    npmLocal.task = npmLocal.task || npm.task(config);
    return Database;
};

This is the module that exposes the main methods of interest such as any, manyOrNone, tx, each just to name a few.

These are implemented via the obj.query and then Bitmasks are used to indicate expected result and hence that gives us the naming pattern. For example, for oneOrNone we pass the flags one | none.

obj.oneOrNone = function (query, values, cb, thisArg) {
  //execute the query and specify the type of result via bitmask
  const v = obj.query.call(this, query, values, npm.result.one | npm.result.none);   
  return transform(v, cb, thisArg); //apply the callback via Promise API
};

Lets take a quick glance at manyOrNone sans comments:

obj.manyOrNone = function (query, values) {
  return obj.query.call(this, query, values, npm.result.many | npm.result.none);
};

Very simple implementation. Clean. Let's take a look at query which is defined at line 228 in database.js. We find that it calls the query module which is loaded in the $npm pattern described in part 1. This is also used in oneOrNone.

// [...]
// Generic query method:
query(query, values, qrm) {
    if (!ctx.db) {
        return $p.reject(new Error(npm.text.queryDisconnected));
    }
    return config.$npm.query.call(this, ctx, query, values, qrm);
},
// [...]

I feel that we have high-level context with regards to how this module works and what it does. To me, the next major module is the query.js module since all query related methods looked at above references that. Lets take a look.

Query Module

The query module is responsible for running SQL queries against the data source. As we saw above, it's the generic (bitmask based version) of the oneOrNone, one, etc methods as known by consumers of pg-promise.

What is exported for context:

module.exports = config => {
  return function (ctx, query, values, qrm) {
    return $query.call(this, ctx, query, values, qrm, config);
  };
};

The high-level flow of this module is as follows:

High-Level flow of pg-promise's query.js module

The module returns a function that, when boiled down, runs the method $query which runs the query through the pg library and returns the results as a promise in the desired format. It's the main implementation, if you will, of the different query methods in database.js, it's behavior being altered by the bitmasks.

Interesting "aside" points

A couple of interesting "aside" points I learned while preparing this part.

  • Event-based APIs are used (such as lib/database.js instead of all promises).
  • There is no promise library built in since the implementor passes it in.

  1. Copyright (c) 2015-2018 Vitaly Tomilov, this work is released under the The MIT License ↩︎

Frank Villasenor

Frank Villasenor

Owner and principal author of this site. Professional Engineering Lead, Software Engineer & Architect working in the Chicagoland area as a consultant. Cert: AWS DevOps Pro
Chicago, IL