Muckraker is a wrapper around the pg-promise library to provide some simple, easily extensable, functionality out of the box.
The options
object may contain the following:
-
connection
: Postgres configuration object passed through directly to thepg
library. -
pg
: Configuration passed directly to thepg-promise
library. -
scriptDir
: Path to the directory containing all of your.sql
and.js
scripts. More on this below. -
timestamps
: Configuration for the timestamp columns. More on this below. -
encrypt
: Encryption options. More on this below.
Execute a query on the root level database. Arguments passed to this method are passed directly to pg-promise's query method
Acquire a database connection with a given (optional) tag then run the passed in function fn
passing the database connection as the first parameter. The connection will be a clone of the root database object, containing the same table and script methods.
Acquire a database connection and begin a transaction, then run the passed in function fn
passing the database connection as the first parameter. The connection will be a clone of the root database object, containing the same table and script methods. After the passed in fn
completes, the transaction will either be committed or aborted depending on if your function throws an error.
The options available are:
-
tag
: Similar to thetask
method, used for identification purposes. -
mode
: A TransactionMode object, used to configure the transaction's behavior.
In addition to the top level methods, muckraker will create a table object for each table in your database and attach it to the top level object. For example, if you have a database named users
and another table named profiles
, your root database object (db
) will have table objects at db.users
and db.profiles
.
Query parameters for each table method are passed as an object. This object may contain any properties you like, though any top level keys that do not match a column in the active table will be ignored. Each property that matches a valid column will add a clause to the WHERE
portion of a query.
The default comparison, used for any value which is not an object, is =
(equals). Other operators are supported by using an object with the matching key, such as db.users.find({ column: { $ne: 'test' } })
. Currently available operators are:
-
$eq
for=
-
$ne
for!=
-
$lt
for<
-
$lte
for<=
-
$gt
for>
-
$gte
for>=
-
$in
forIN
-
$nin
forNOT IN
-
$like
forLIKE
-
$nlike
forNOT LIKE
Multiple properties passed together are joined with AND
statements.
In addition to the above simple queries, a deeper object may be passed representing an operation on a property within a json
or jsonb
column. For example:
db.users.find({ preferences: { some: { really: { deep: { property: { $ne: null } } } } } })
Would yield a query similar to SELECT * FROM "users" WHERE "preferences"#>>'{some,really,deep,property}' IS NOT NULL
Each table method, with the exception of destroy
, accepts a final parameter columns
. This parameter is optional and should be an array of column names you wish to have returned. By default, every column in the table that is not encrypted will be returned.
In order to receive encrypted values you must specify the columns
parameter and include the name of the encrypted column.
In addition, you may return a child property of a json
or jsonb
column by passing an array to this property representing the path to the key. For example:
table.find({ public: true }, ['id', ['a-json-column', 'and', 'its', 'key']])
Would return an array of objects with two properties id
and key
(named for the final property in the json path chain).
Muckraker will attempt to automatically update known columns for created, updated and deleted timestamps.
These column names default to created_at
, updated_at
, and deleted_at
. You may change these options both globally as well as specifying them on a per-table basis when initializing the database object. For example:
const db = new Muckraker({
timestamps: {
created: 'created_at', // global defaults
updated: 'updated_at',
deleted: 'deleted_at',
users: {
created: 'created', // overrides for only the "users" table
updated: 'updated',
deleted: 'deleted'
}
}
})
When using the insert()
method, both the created and updated timestamps will be set to the current time by default. If you manually specify a value for either as part of your data object that value will take precedence.
When using the update()
or updateOne()
methods, the updated timestamp will be set to the current time. Again, if a value for the column is specified it will take precedence.
The deleted timestamp is used to implement soft deletes. By default every query will specify a WHERE deleted_at IS NULL
clause. This can be overridden by manually specifying an operator for the column, such as table.find({ deleted_at: { $ne: null } })
. If the deleted column exists, the destroy()
method will default to populating the column with the current timestamp instead of actually deleting the row. If you wish to forcefully delete, even when typically using soft deletes, you may pass the object { force: true }
as the second parameter to the destroy()
method. For example: table.destroy({ id: 0 }, { force: true })
.
Muckraker has some basic support for encryption via the pgcrypto
extension and its pgp_sym_encrypt
and pgp_sym_decrypt
methods. To use it, you must inform muckraker about what columns are encrypted:
const db = new Muckraker({
encrypt: {
'users.secret': { key: 'some_secret_key' }
}
});
The above configuration tells muckraker that the "secret"
column in the "users"
table is encrypted. In order for this to work correctly, you must load the pgcrypto
extension by running CREATE EXTENSION "pgcrypto";
. You must also set the column type of the encrypted column to BYTEA
.
Now when the "secret"
column is written to, its value will be encrypted by postgres:
db.users.insert({ name: 'test', secret: 'some super secret value' });
// writes the result of pgp_sym_encrypt('some super secret value', 'some_secret_key', 'cipher-algo=aes256') to the "secret" column
When performing queries, by default any encrypted columns are not returned. This is to help prevent accidentally leaking sensitive data. If you would like to return an encrypted column, pass an array of column names manually and muckraker will decrypt the value and return it for you:
db.users.findOne({ name: 'test' }, ['name', 'secret']);
// returns { name: 'test', secret: 'some super secret value' }
The default cipher of aes256
may be overridden globally by passing a cipher
property in the options, as well as per-column by passing it next to the key
property:
const db = new Muckraker({
encrypt: {
'users.secret': { key: 'some_secret_key' }, // uses the default cipher
'users.extraSecret': { cipher: 'aes512', key: 'another_secret_key' }, // uses its own cipher
cipher: 'aes192' // default
},
});
Perform a SELECT
query returning any number of rows.
Perform a SELECT
query returning either exactly one row, or null
.
Perform an INSERT
using the given data.
Perform a DELETE
. The options
object may specify a force
boolean, which when true
will perform a hard delete even when a timestamp column representing soft deletes exists.
Perform an UPDATE
query matching any number of rows using params
as the WHERE
and data
as the new values.
Perform an UPDATE
query matching either exactly one row, or none using params
as the WHERE
and data
as the new values.
Sometimes writing long queries can be cumbersome, so muckraker can also load queries from both *.sql
and *.js
files. By default, these files will be loaded recursively from a directory named db
in the current working directory at initialization time. You may override this by using the scriptDir
property passed to the Muckraker
constructor.
Script files may be located either directly in the db
directory, which will yield in functions being attached directly to the root database instance, or contained within a directory with a name matching a table which will attach the function to the corresponding table object instead. Scripts contained in directories that do not match a table will be ignored.
When using *.sql
files, you may use yaml frontmatter to specify some additional configuration items while *.js
files provide this configuration via properties in an exported object.
A *.sql
file that returns either exactly one row or null
might look like:
---
name: myFunction
returns: one || none
---
SELECT * FROM "users" WHERE id = $[id]
While the corresponding *.js
file could look like:
exports.name = 'myFunction'
exports.returns = 'one || none'
exports.query = 'SELECT * FROM "users" WHERE id = $[id]'
// this query could also be defined as a function like so
/*
exports.execute = function (db, { id }) {
return db.users.findOne({ id })
}
*/
Each script may specify:
-
name
: A string used to name a given function. If not specified, this defaults to the filename of the script. -
returns
: A string describing the number of rows this query is intended to return. Available values areone
,many
,none
, andany
. Multiple values can be appended using the characters||
, for exampleone || none
. The meaning of each of these values can be found in the pg-promise docs -
transaction
: An object describing transaction options. This may also be the valuetrue
to provide a simple transaction. Available properties on this key are:-
tag
: For identification, defaults to the same value asname
. -
isolation
: Specify isolation level, see here for possible values to be passed as a string. -
readOnly
: A boolean, whentrue
specifies the transaction to beREAD ONLY
. -
deferrable
: A boolean, whentrue
specifies the transaction to beDEFERRABLE
.
-
As well as one of the following (note that *.sql
files pass text after the frontmatter as an implicit query
property):
-
execute
: A function to be run. This will be passed exactly two arguments, the first being an instance of thedb
, the second being user specified input. -
query
: A string SQL query following the conventions of pg-promise queries