Skip to content

Commit

Permalink
style: compartmentalize project structure
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Nov 14, 2018
1 parent 5d8410d commit 86e0d2c
Show file tree
Hide file tree
Showing 29 changed files with 847 additions and 684 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
],
"root": true,
"rules": {
"no-continue": 0,
"no-restricted-syntax": 0,
"no-unused-expressions": [
2,
Expand Down
22 changes: 22 additions & 0 deletions src/connectionMethods/any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// @flow

import {
createUlid
} from '../utilities';
import type {
InternalQueryAnyFunctionType
} from '../types';
import query from './query';

/**
* Makes a query and expects any number of results.
*/
const any: InternalQueryAnyFunctionType = async (connection, clientConfiguration, rawSql, values, queryId = createUlid()) => {
const {
rows
} = await query(connection, clientConfiguration, rawSql, values, queryId);

return rows;
};

export default any;
47 changes: 47 additions & 0 deletions src/connectionMethods/anyFirst.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @flow

import {
createUlid
} from '../utilities';
import {
DataIntegrityError
} from '../errors';
import type {
InternalQueryAnyFirstFunctionType
} from '../types';
import log from '../Logger';
import any from './any';

const anyFirst: InternalQueryAnyFirstFunctionType = async (connection, clientConfigurationType, rawSql, values, queryId = createUlid()) => {
const rows = await any(connection, clientConfigurationType, rawSql, values, queryId);

if (rows.length === 0) {
return [];
}

const keys = Object.keys(rows[0]);

if (keys.length !== 1) {
log.error({
queryId
}, 'DataIntegrityError');

throw new DataIntegrityError();
}

const firstColumnName = keys[0];

if (typeof firstColumnName !== 'string') {
log.error({
queryId
}, 'DataIntegrityError');

throw new DataIntegrityError();
}

return rows.map((row) => {
return row[firstColumnName];
});
};

export default anyFirst;
113 changes: 113 additions & 0 deletions src/connectionMethods/createConnection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// @flow

import pg from 'pg';
import serializeError from 'serialize-error';
import {
parse as parseConnectionString
} from 'pg-connection-string';
import {
mapTaggedTemplateLiteralInvocation
} from '../utilities';
import type {
ClientConfigurationType,
DatabaseConfigurationType,
DatabaseSingleConnectionType
} from '../types';
import log from '../Logger';
import any from './any';
import anyFirst from './anyFirst';
import many from './many';
import manyFirst from './manyFirst';
import maybeOne from './maybeOne';
import maybeOneFirst from './maybeOneFirst';
import one from './one';
import oneFirst from './oneFirst';
import query from './query';
import transaction from './transaction';

// @see https://github.com/facebook/flow/issues/2977#issuecomment-390613203
const defaultClientConfiguration = Object.freeze({});

export default async (
connectionConfiguration: DatabaseConfigurationType,
clientConfiguration: ClientConfigurationType = defaultClientConfiguration
): Promise<DatabaseSingleConnectionType> => {
const pool = new pg.Pool(typeof connectionConfiguration === 'string' ? parseConnectionString(connectionConfiguration) : connectionConfiguration);

pool.on('error', (error) => {
log.error({
error: serializeError(error)
}, 'client connection error');
});

pool.on('connect', (client) => {
log.info({
processId: client.processID,
stats: {
idleConnectionCount: pool.idleCount,
totalConnectionCount: pool.totalCount,
waitingRequestCount: pool.waitingCount
}
}, 'created a new client connection');
});

pool.on('acquire', (client) => {
log.info({
processId: client.processID,
stats: {
idleConnectionCount: pool.idleCount,
totalConnectionCount: pool.totalCount,
waitingRequestCount: pool.waitingCount
}
}, 'client is checked out from the pool');
});

pool.on('remove', (client) => {
log.info({
processId: client.processID,
stats: {
idleConnectionCount: pool.idleCount,
totalConnectionCount: pool.totalCount,
waitingRequestCount: pool.waitingCount
}
}, 'client connection is closed and removed from the client pool');
});

const connection = await pool.connect();

connection.on('notice', (notice) => {
log.info({
notice
}, 'notice message');
});

let ended = false;

const bindConnection = {
any: mapTaggedTemplateLiteralInvocation(any.bind(null, connection, clientConfiguration)),
anyFirst: mapTaggedTemplateLiteralInvocation(anyFirst.bind(null, connection, clientConfiguration)),
end: async () => {
if (ended) {
return ended;
}

await connection.release();

ended = pool.end();

return ended;
},
many: mapTaggedTemplateLiteralInvocation(many.bind(null, connection, clientConfiguration)),
manyFirst: mapTaggedTemplateLiteralInvocation(manyFirst.bind(null, connection, clientConfiguration)),
maybeOne: mapTaggedTemplateLiteralInvocation(maybeOne.bind(null, connection, clientConfiguration)),
maybeOneFirst: mapTaggedTemplateLiteralInvocation(maybeOneFirst.bind(null, connection, clientConfiguration)),
one: mapTaggedTemplateLiteralInvocation(one.bind(null, connection, clientConfiguration)),
oneFirst: mapTaggedTemplateLiteralInvocation(oneFirst.bind(null, connection, clientConfiguration)),
query: mapTaggedTemplateLiteralInvocation(query.bind(null, connection, clientConfiguration)),
transaction: (handler) => {
return transaction(bindConnection, handler);
}
};

return bindConnection;
};
133 changes: 133 additions & 0 deletions src/connectionMethods/createPool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// @flow

import pg from 'pg';
import serializeError from 'serialize-error';
import {
parse as parseConnectionString
} from 'pg-connection-string';
import {
mapTaggedTemplateLiteralInvocation
} from '../utilities';
import type {
ClientConfigurationType,
DatabasePoolType,
DatabaseConfigurationType
} from '../types';
import log from '../Logger';
import any from './any';
import anyFirst from './anyFirst';
import many from './many';
import manyFirst from './manyFirst';
import maybeOne from './maybeOne';
import maybeOneFirst from './maybeOneFirst';
import one from './one';
import oneFirst from './oneFirst';
import query from './query';
import transaction from './transaction';

// @see https://github.com/facebook/flow/issues/2977#issuecomment-390613203
const defaultClientConfiguration = Object.freeze({});

export default (
connectionConfiguration: DatabaseConfigurationType,
clientConfiguration: ClientConfigurationType = defaultClientConfiguration
): DatabasePoolType => {
const pool = new pg.Pool(typeof connectionConfiguration === 'string' ? parseConnectionString(connectionConfiguration) : connectionConfiguration);

pool.on('error', (error) => {
log.error({
error: serializeError(error)
}, 'client connection error');
});

pool.on('connect', (client) => {
log.info({
processId: client.processID,
stats: {
idleConnectionCount: pool.idleCount,
totalConnectionCount: pool.totalCount,
waitingRequestCount: pool.waitingCount
}
}, 'created a new client connection');
});

pool.on('acquire', (client) => {
log.info({
processId: client.processID,
stats: {
idleConnectionCount: pool.idleCount,
totalConnectionCount: pool.totalCount,
waitingRequestCount: pool.waitingCount
}
}, 'client is checked out from the pool');
});

pool.on('remove', (client) => {
log.info({
processId: client.processID,
stats: {
idleConnectionCount: pool.idleCount,
totalConnectionCount: pool.totalCount,
waitingRequestCount: pool.waitingCount
}
}, 'client connection is closed and removed from the client pool');
});

const connect = async () => {
const connection = await pool.connect();

connection.on('notice', (notice) => {
log.info({
notice
}, 'notice message');
});

const bindConnection = {
any: mapTaggedTemplateLiteralInvocation(any.bind(null, connection, clientConfiguration)),
anyFirst: mapTaggedTemplateLiteralInvocation(anyFirst.bind(null, connection, clientConfiguration)),
many: mapTaggedTemplateLiteralInvocation(many.bind(null, connection, clientConfiguration)),
manyFirst: mapTaggedTemplateLiteralInvocation(manyFirst.bind(null, connection, clientConfiguration)),
maybeOne: mapTaggedTemplateLiteralInvocation(maybeOne.bind(null, connection, clientConfiguration)),
maybeOneFirst: mapTaggedTemplateLiteralInvocation(maybeOneFirst.bind(null, connection, clientConfiguration)),
one: mapTaggedTemplateLiteralInvocation(one.bind(null, connection, clientConfiguration)),
oneFirst: mapTaggedTemplateLiteralInvocation(oneFirst.bind(null, connection, clientConfiguration)),
query: mapTaggedTemplateLiteralInvocation(query.bind(null, connection, clientConfiguration)),
release: connection.release.bind(connection),
transaction: (handler) => {
return transaction(bindConnection, handler);
}
};

return bindConnection;
};

return {
any: mapTaggedTemplateLiteralInvocation(any.bind(null, pool, clientConfiguration)),
anyFirst: mapTaggedTemplateLiteralInvocation(anyFirst.bind(null, pool, clientConfiguration)),
connect,
many: mapTaggedTemplateLiteralInvocation(many.bind(null, pool, clientConfiguration)),
manyFirst: mapTaggedTemplateLiteralInvocation(manyFirst.bind(null, pool, clientConfiguration)),
maybeOne: mapTaggedTemplateLiteralInvocation(maybeOne.bind(null, pool, clientConfiguration)),
maybeOneFirst: mapTaggedTemplateLiteralInvocation(maybeOneFirst.bind(null, pool, clientConfiguration)),
one: mapTaggedTemplateLiteralInvocation(one.bind(null, pool, clientConfiguration)),
oneFirst: mapTaggedTemplateLiteralInvocation(oneFirst.bind(null, pool, clientConfiguration)),
query: mapTaggedTemplateLiteralInvocation(query.bind(null, pool, clientConfiguration)),
transaction: async (handler) => {
log.debug('allocating a new connection to execute the transaction');

const connection = await connect();

let result;

try {
result = await connection.transaction(handler);
} finally {
log.debug('releasing the connection that was earlier secured to execute a transaction');

await connection.release();
}

return result;
}
};
};
4 changes: 4 additions & 0 deletions src/connectionMethods/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

export {default as createConnection} from './createConnection';
export {default as createPool} from './createPool';
36 changes: 36 additions & 0 deletions src/connectionMethods/many.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// @flow

import {
createUlid
} from '../utilities';
import {
NotFoundError
} from '../errors';
import type {
InternalQueryManyFunctionType
} from '../types';
import log from '../Logger';
import query from './query';

/**
* Makes a query and expects at least 1 result.
*
* @throws NotFoundError If query returns no rows.
*/
const many: InternalQueryManyFunctionType = async (connection, clientConfiguration, rawSql, values, queryId = createUlid()) => {
const {
rows
} = await query(connection, clientConfiguration, rawSql, values, queryId);

if (rows.length === 0) {
log.error({
queryId
}, 'NotFoundError');

throw new NotFoundError();
}

return rows;
};

export default many;
Loading

0 comments on commit 86e0d2c

Please sign in to comment.