Pg-promise - Reusable queries that may participate in a transaction/task - javascript

I am trying to achieve a reuse pattern where query functions may receive a parameter trx=tx/task if they are participating in an existing transaction/task, and reuse that tx/task context... otherwise if trx=undefined passed in, creates a new task for the queries. The idea is for the function to be agnostic of whether it is being used singularly, or is participating in a higher-order block transaction.
What I desire (ideally) is a promise-function that will return a task context so that I can write clean like the following (which doesn't work):
async function trxRunQueries(trx:ITask<any>|undefined = undefined):Promise<any[]>
{
if(!trx)
trx=await db.task(); // if !trx then create new task context from db
const dataset1 = await trx.many(`select * from tableA`);
const dataset2 = await trx.many(`select * from tableB`);
return [dataset1,dataset2];
}
However seems like db.task() needs to execute the contextual queries in the cb parameter, but it leaves me hanging & wondering how I can achieve the desired pattern without writing the code out twice -- once with db.task(trx => ) wrapper, and another executing trx.many(...) directly.
I was wondering if it is ok to do something hacky, like below, to achieve this pattern of participating-optionally-in-a-transaction, and will it work (or is it really not a recommended way of doing things) -- or is there a better way that I am not thinking of?
async function runQueries(trx:ITask<any>):Promise<any[]>
{
const dataset1 = await trx.many(`select * from tableA`);
const dataset2 = await trx.many(`select * from tableB`);
return [dataset1,dataset2];
}
async function trxRunQueries(trx:ITask<any>|undefined = undefined ):Promise<any[]>
{
let result:any[]=[];
try {
// If trx not passed in the create task context
if(!trx)
await db.task(async trx => {result=await runQueries(trx)})
else
result=await runQueries(trx);
return result;
}
catch (err) {
throw(err);
}
}

Such pattern is implemented by pg-promise-demo, which uses event extend to extend the database protocol with context-agnostic entity repositories.

Related

What's the advantage of fastify-plugin over a normal function call?

This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones

Making a custom group of defined chaining methods in js

The question is related to general js programming, but I'll use nightwatch.js as an example to elaborate my query.
NightWatch JS provides various chaining methods for its browser components, like: -
browser
.setValue('input[name='email']','example#mail.com')
.setValue('input[name='password']', '123456')
.click('#submitButton')
But if I'm writing method to select an option from dropdown, it requires multiple steps, and if there are multiple dropdowns in a form, it gets really confusing, like: -
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
Is it possible to create a custom chaining method to group these already defined methods? For example something like:
/* custom method */
const dropdownSelector = (id, value) {
return this
.click(`${id}`).
.waitForElementVisible(`${value}`)
.click(`${value}`)
}
/* So it can be used as a chaining method */
browser
.dropdownSelector('country', 'india')
.dropdownSelector('state', 'delhi')
Or is there any other way I can solve my problem of increasing reusability and readability of my code?
I'm somewhat new to JS so couldn't tell you an ideal code solution, would have to admit I don't know what a proxy is in this context. But in the world of Nightwatch and test-automation i'd normally wrap multiple steps I plan on reusing into a page object. Create a new file in a pageObject folder and fill it with the method you want to reuse
So your test...
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
becomes a page object method in another file called 'myObject' like...
selectLocation(browser, country, state, city) {
browser
.click(`#country`) <== assume this never changes?
.waitForElementVisible(country)
.click(country)
.click(state)
.waitForElementVisible(city)
.click(city);
}
and then each of your tests inherit the method and define those values themselves, however you chose to manage that...
const myObject = require ('<path to the new pageObject file>')
module.exports = {
'someTest': function (browser) {
const country = 'something'
const state = 'something'
const city = 'something'
myObject.selectLocation(browser);
You can also set your country / state / city as variables in a globals file and set them as same for everything but I don't know how granular you want to be.
Hope that made some sense :)
This is a great place to use a Proxy. Given some class:
function Apple ()
{
this.eat = function ()
{
console.log("I was eaten!");
return this;
}
this.nomnom = function ()
{
console.log("Nom nom!");
return this;
}
}
And a set of "extension methods":
const appleExtensions =
{
eatAndNomnom ()
{
this.eat().nomnom();
return this;
}
}
We can create function which returns a Proxy to select which properties are retrieved from the extension object and which are retrieved from the originating object:
function makeExtendedTarget(target, extensions)
{
return new Proxy(target,
{
get (obj, prop)
{
if (prop in extensions)
{
return extensions[prop];
}
return obj[prop];
}
});
}
And we can use it like so:
let apple = makeExtendedTarget(new Apple(), appleExtensions);
apple
.eatAndNomnom()
.eat();
// => "I was eaten!"
// "Nom nom!"
// "I was eaten!"
Of course, this requires you to call makeExtendedTarget whenever you want to create a new Apple. However, I would consider this a plus, as it makes it abundantly clear you are created an extended object, and to expect to be able to call methods not normally available on the class API.
Of course, whether or not you should be doing this is an entirely different discussion!

Getting around an async/await issue

I am creating a simple node script to learn the functionality of Cosmos DB. I want to create a way to not have to provide the following at the top of every async function (yes, I know I could chain the async calls with but that means still means I have to use a new db instance at the top of every function. So, I want to do something like this:
const {database} = await client.databases.createIfNotExists({id: databaseId});
const {container} = await database.containers.createIfNotExists({id: containerId});
With that said, I've bumped my head on this for a few hours and can't find a way to create one database and one container for all my functions to share. The idea (but not implementation because it doesn't work, is to do something like this:
getConnections = async () => {
const {database} = await client.databases.createIfNotExists({id: databaseId});
const {container} = await database.containers.createIfNotExists({id: containerId});
let connections = {};
connections.db = database;
connections.container = container;
return connections;
};
But since the getCoonections method is async (which it must be because the methods that would use it are, too) the function doesn't necessarily finish before the first insert is made in another function, thereby causing an exception.
Has anyone found a way to centralize these objects so I don't have to declare them in each async function of my app?
It sounds like you need to get these connections before the app does anything else. So why not simply make the loading of your app use async/await too?
async function init() {
const connections = await getConnections();
const app = initializeTheRestOfYourApp(connections); // now safe to do inserts
};
init();
This pretty much works now, Not sure why as there is no blocking between this init() and the next async method in the call chain uses the connection, but it's working. – David Starr - Elegant Code just now

How do I implement a find-or-create pattern in firestore

The firestore api has me a little mixed up in trying to have a repeatable pattern for find-or-create style functions. I'd like the canonical version to look like this:
// returns a promise resolving to a DocumentSnapshot (I think??)
function findOrCreateMyObject(myObject) {
return findMyObject(myObject.identifier).then(documentSnapshot => {
return (documentSnapshot)? documentSnapshot : createMyObject(myObject);
});
};
I'm not sure if DocumentSnapshot is the appropriate return from this, but I figure the caller may want to inspect or update the result, like this:
return findOrCreateMyObject({ identifier:'foo' }).then(documentSnapshot => {
console.log(documentSnapshot.data.someProperty);
return documentSnapshot.ref.update({ someProperty:'bar' });
});
Assuming I am right about that (please tell me if not), it means that both the find and create functions must return a DocumentSnapshot. This is easy enough for the find...
function findMyObject(identifier) {
let query = db.collection('my-object-collection').where('identifier','=='identifier);
return query.get().then(querySnapshot => {
return (querySnapshot.docs.length)? querySnapshot.docs[0] : null;
});
}
...but rather awkward for the create, and the the gist of my problem. I'd want to write create like this....
function createMyObject(myObject) {
// get db from admin
let collectionRef = db.collection('my-object-collection');
return db.collection('my-object-collection').doc().set(myObject);
}
But I cannot because DocumentReference set() resolves to a "non-null promise containing void". Void? I must read back the object I just wrote in order to get a reference to it? In other words, my idealized create needs to be rewritten to be slower and clunkier...
function createMyObject(myObject) {
// get db from admin
let collectionRef = db.collection('my-object-collection');
return db.collection('my-object-collection').doc().set(myObject).then(() => {
// must we query the thing just written?
return findMyObject(myObject.identifier); // frowny face
});
}
This makes my generic create longer (and unnecessarily so when doing just a create). Please tell me:
is DocumentSnapshot the right "currency" for these functions to traffic in?
Am I stuck with a set() and then another query when creating the new object?
Thanks!
EDIT As an example of where I would apply this, say I have customers, uniquely identified by email, and they have a status: 'gold', 'silver' or 'bronze'. My CRM system decides that someone identifying himself as doug#stevenson.com deserves 'silver' status. We don't know at this point wither Mr. Stevenson is a current customer, so we do a find-or-create...
findOrCreateCustomer({ email:'doug#stevenson.com' }).then(customer => {
customer.update({ status:'silver' });
});
I wouldn't just create, because the customer might exist. I wouldn't just update, because the customer might not exist, or might not meet some other criterion for the update.

Stubbing variables in a constructor?

I'm trying to figure out how to properly stub this scenario, but i'm a little stuck.
The scenario is, i've got a db.js file that has a list of couchdb databases in it (each database contains tweet entries for a particular year).
Each year a new database is created and added to this list to hold the new entries for that year (so the list of databases isn't constant, it changes each year).
So my db.js file looks like this:
var nano = require('nano')(`http://${host}`);
var databaseList = {
db1: nano.use('db2012'),
db2: nano.use('db2013'),
db4: nano.use('db2014'),
db5: nano.use('db2015'),
db6: nano.use('db2016')
};
module.exports.connection = nano;
module.exports.databaseList = databaseList;
And event.js (a simple model file), before methods are added looks like this:
var lastInObject = require('../../helpers/last_in_object');
var db = require('../../db');
var EventModel = function EventModel() {
this.connection = db.connection;
this.databaseList = db.databaseList;
this.defaultDatabase = lastInObject(db.databaseList);
};
EventModel.prototype.findAll =
function findAll(db, callback) {/* ... */}
My question is, how do i stub the databaseList, so i can safely test each of the model methods without having any brittleness from the growing databaseList object?
Ideally i'd like to be able hijack the contents of the databaseList in my tests, to mock different scenarios, but i'm unsure how to tackle it.
Here's an example test, to ensure the defaultDatabase property is always pointing to the last known event, but obviously i don't want to have to update this test every year, when databaseList changes, as that makes the tests very brittle.
it('should set the default database to the last known event', () => {
var Event = require('../event');
var newEventModel = new Event();
expect(newEventModel.defaultDatabase.config.db)
.to.equal('db2014');
});
Suggestions welcome! If i've gone about this wrong, let me know what i've done and how i can approach it!
Also, this is just a scenario, i do have tests for lastInObject, i'm more interested in how to mock the concerning data.
In my opinion you need to stub the whole "db" module. That way you won't have any real db connection and you can easily control the environment of your tests. You can achieve this by using the mockery module.
That way you can stub the object that the require('../../db') returns. This will allow you to set whatever value you like in the properties of that object.
Building upon Scotty's comment in the accepted answer a bit... here's some code I've got which seems to do the job. No guarantees that this is a good implementation, but it does successfully stub out the insert and get methods. Hopefully it helps someone. :)
// /src/common/database.js
const database = require('nano')('http://127.0.0.1/database');
module.exports = {
async write(document, documentId) {
return await new Promise(resolve => database.insert(document, documentId, resolve));
},
async read(documentId){
return await new Promise(resolve => database.get(documentId, resolve));
}
};
// /test/setup.js
const proxyquire = require('proxyquire');
proxyquire('../src/common/database.js', {
nano() {
return {
insert(document, documentId, callback){ callback(); },
get(documentId, callback){ callback(); }
};
}
});

Categories

Resources