2
\$\begingroup\$

I've been using the excellent pg-promise library for a few years now, but my primary irk with it is that the stack traces are sometimes unhelpful. For example, the following test will fail (currently on nodejs v16.x):

  const pgdb = pgp(config) // see pg-promise link for details, but just know that various db call methods are exposed as below:

  it('should print a stack trace that includes all calling functions from a db call', async () => {
    async function one() {
      await two()
    }

    async function two() {
      await three()
    }
    async function three() {
      await pgdb.result('SELECT * FROM does_not_exist')
    }

    try {
      await one()
      assert.fail(`call to function 'one' should have thrown an error`)
    } catch (err) {
      const stack = err.stack
      assert.include(stack, 'three', `could not find call to function 'three' in stack`)
      assert.include(stack, 'two', `could not find call to function 'two' in stack`)
      assert.include(stack, 'one', `could not find call to function 'one' in stack`)
    }
)

This is because the stack comes out like this:

error: relation "does_not_exist" does not exist
    at Parser.parseErrorMessage (/app/node_modules/pg-protocol/dist/parser.js:287:98)
    at Parser.handlePacket (/app/node_modules/pg-protocol/dist/parser.js:126:29)
    at Parser.parse (/app/node_modules/pg-protocol/dist/parser.js:39:38)
    at Socket.<anonymous> (/app/node_modules/pg-protocol/dist/index.js:11:42)
    at Socket.emit (node:events:390:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at Socket.Readable.push (node:internal/streams/readable:228:10)
    at TCP.onStreamRead (node:internal/stream_base_commons:199:23)
    at TCP.callbackTrampoline (node:internal/async_hooks:130:17)

The stack trace has no context for where this was actually thrown. To fix this problem the author recommends using Bluebird promises for the better stack-trace capabilities, but recent advancements in node.js have rendered async/await better for performance.

I understand why this is happening, but it doesn't make my job easier. So, I thought I could us a Proxy to wrap the root database client object to get a proper stack trace:

// initializer module to do all the database client setup
const intermediate = pgp(_config)  // create an intermediate client

// create a new object; creating a proxy from `intermediate` will throw 
// `property 'result' is a read-only and non-configurable data property` errors 
// when accessed through a proxy 
const _pgdb = { ...intermediate } 

const handler = {
  get: function (target, prop) {
    if (prop in target) {
      const property = target[prop]
      if (typeof property === 'function') {
        return async function (...args) {
          try {
            return await property.apply(target, args)
          } catch (err) {
            throw new Error(`error calling database function '${prop}': ${err.message}`)
          }
        }
      }
      return property
    }
    // there are some properties like `$config` that aren't destructured from `intermediate`
    if (prop in intermediate) {
      return intermediate[prop]
    }

    return undefined
  }
}

const pgdb = new Proxy(_pgdb, handler)

Stack traces now look like:

Error: error calling database function 'result': relation "does_not_exist" does not exist
    at Proxy.<anonymous> (/app/db/initializer.js:109:19)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async three (/app/test/dbal/utils.test.js:252:7)
    at async two (/app/test/dbal/utils.test.js:249:7)
    at async one (/app/test/dbal/utils.test.js:245:7)
    at async Context.<anonymous> (/app/test/dbal/utils.test.js:256:7)

While this works and gives me the exact lines where the failure was thrown, it seems like a dirty hack and I feel like I'm missing something that could make this wrapper a little more elegant.

\$\endgroup\$
2
  • \$\begingroup\$ Of course the first one fails, you've got an unmatched parenthesis! \$\endgroup\$ Commented Mar 19, 2022 at 5:15
  • \$\begingroup\$ If that were true, that’s not the stacktrace I’d be getting :) apologies for the partial snippet \$\endgroup\$ Commented Mar 19, 2022 at 7:08

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.