Skip to content

Commit

Permalink
Merge pull request #65 from leapfrogtechnology/consistent-connections
Browse files Browse the repository at this point in the history
Make connections config consistent with Knex's config (breaking change)
  • Loading branch information
kabirbaidhya authored Apr 27, 2020
2 parents b755f8b + 6c21dcf commit 32f7536
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 63 deletions.
14 changes: 8 additions & 6 deletions examples/node-app-mssql/connections.sync-db.json.docker
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
"connections": [
{
"id": "testdb1",
"host": "mssql",
"port": 1433,
"user": "sa",
"database": "master",
"password": "Password@123",
"client": "mssql"
"client": "mssql",
"connection": {
"host": "mssql",
"port": 1433,
"user": "sa",
"database": "master",
"password": "Password@123"
}
}
]
}
14 changes: 8 additions & 6 deletions examples/node-app-mssql/connections.sync-db.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
"connections": [
{
"id": "testdb1",
"host": "localhost",
"port": 1433,
"user": "db1user",
"database": "testdb1",
"password": "password",
"client": "mssql"
"client": "mssql",
"connection": {
"host": "localhost",
"port": 1433,
"user": "db1user",
"database": "testdb1",
"password": "password"
}
}
]
}
43 changes: 29 additions & 14 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { mergeDeepRight } from 'ramda';
import * as fs from './util/fs';
import { log } from './util/logger';
import { isObject } from './util/types';
import DbConfig from './domain/DbConfig';
import Configuration from './domain/Configuration';
import ConnectionConfig from './domain/ConnectionConfig';
import ConnectionsFileSchema from './domain/ConnectionsFileSchema';
import { prepareInjectionConfigVars } from './service/configInjection';
import { DEFAULT_CONFIG, CONFIG_FILENAME, CONNECTIONS_FILENAME, REQUIRED_ENV_KEYS } from './constants';

Expand Down Expand Up @@ -96,7 +96,14 @@ export async function resolveConnections(): Promise<ConnectionConfig[]> {

log(
'Resolved connections: %O',
connections.map(({ id, host, database }) => ({ id, host, database }))
connections.map(({ id, client, connection }) => ({
id,
client,
connection: {
host: (connection as any).host,
database: (connection as any).database
}
}))
);

return connections;
Expand All @@ -109,7 +116,13 @@ export async function resolveConnections(): Promise<ConnectionConfig[]> {
* @returns {string}
*/
export function getConnectionId(connectionConfig: ConnectionConfig): string {
return connectionConfig.id || `${connectionConfig.host}/${connectionConfig.database}`;
if (connectionConfig.id) {
return connectionConfig.id;
}

const { host, database } = connectionConfig.connection as any;

return host && database ? `${host}/${database}` : '';
}

/**
Expand All @@ -136,20 +149,22 @@ export function resolveConnectionsFromEnv(): ConnectionConfig[] {

validateConnections(REQUIRED_ENV_KEYS);

const connection = {
client: process.env.DB_CLIENT,
const connectionConfig = {
id: process.env.DB_ID,
host: process.env.DB_HOST,
port: process.env.DB_PORT ? +process.env.DB_PORT : null,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
options: {
encrypt: process.env.DB_ENCRYPTION === 'true'
client: process.env.DB_CLIENT,
connection: {
host: process.env.DB_HOST,
port: process.env.DB_PORT ? +process.env.DB_PORT : null,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
options: {
encrypt: process.env.DB_ENCRYPTION === 'true'
}
}
} as ConnectionConfig;

return [connection];
return [connectionConfig];
}

/**
Expand All @@ -162,7 +177,7 @@ async function resolveConnectionsFromFile(filename: string): Promise<ConnectionC
log('Resolving file: %s', filename);

const loaded = await fs.read(filename);
const { connections } = JSON.parse(loaded) as DbConfig;
const { connections } = JSON.parse(loaded) as ConnectionsFileSchema;

// TODO: Validate the connections received from file.

Expand Down
16 changes: 7 additions & 9 deletions src/domain/ConnectionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ import * as Knex from 'knex';
/**
* Database connection configuration.
*/
type KnexConnections = Knex.ConnectionConfig &
Knex.MariaSqlConnectionConfig &
Knex.MySqlConnectionConfig &
Knex.MsSqlConnectionConfig &
Knex.SocketConnectionConfig &
Knex.Sqlite3ConnectionConfig;

type ConnectionConfig = KnexConnections & {
interface ConnectionConfig {
id?: string;
client: string;
};
// This could be anything that Knex supports:
// - a connection string
// - a Knex.Config.connection config object
// Additionally, we also support providing a direct Knex connection instance for programmatic API.
connection: string | Knex | Knex.StaticConnectionConfig | Knex.ConnectionConfigProvider;
}

export default ConnectionConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import ConnectionConfig from './ConnectionConfig';
/**
* Interface for sync-db connection file (connections.sync-db.json).
*/
interface DbConfig {
interface ConnectionsFileSchema {
connections: ConnectionConfig[];
}

export default DbConfig;
export default ConnectionsFileSchema;
39 changes: 23 additions & 16 deletions src/util/db.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as Knex from 'knex';
import { dbLogger, log } from './logger';
import { getConnectionId } from '../config';
import ConnectionConfig from '../domain/ConnectionConfig';
import { dbLogger, log } from './logger';
import ConnectionReference from '../domain/ConnectionReference';

/**
* Database connections given by the user or the CLI frontend.
*/
export type DatabaseConnections = ConnectionConfig[] | ConnectionConfig | Knex[] | Knex;
export type DatabaseConnections = ConnectionConfig[] | ConnectionConfig;

/**
* Returns true if the provided object is a knex connection instance.
Expand Down Expand Up @@ -38,12 +38,24 @@ export function getConfig(db: Knex): ConnectionConfig {

/**
* Create a new connection instance (knex) using the connection config.
* Throws error if the config already holds a Knex's instance.
*
* @param {ConnectionConfig} config
* @returns {Knex}
*/
export function createInstance(config: ConnectionConfig): Knex {
return Knex({ connection: config, client: config.client });
if (isKnexInstance(config.connection)) {
throw new Error('The provided connection already contains a connection instance.');
}

const { host, database } = config.connection as any;

log(`Connecting to database: ${host}/${database}`);

return Knex({
client: config.client,
connection: config.connection
});
}

/**
Expand Down Expand Up @@ -73,24 +85,19 @@ export function withTransaction<T>(
/**
* Map user provided connection(s) to the connection instances.
*
* @param {(DatabaseConnections)} conn
* @param {(DatabaseConnections)} connectionConfig
* @returns {ConnectionReference[]}
*/
export function mapToConnectionReferences(conn: DatabaseConnections): ConnectionReference[] {
const connectionList = Array.isArray(conn) ? conn : [conn];
export function mapToConnectionReferences(connectionConfig: DatabaseConnections): ConnectionReference[] {
const list = Array.isArray(connectionConfig) ? connectionConfig : [connectionConfig];

return connectionList.map(connection => {
if (isKnexInstance(connection)) {
log(`Received connection instance to database: ${connection.client.config.connection.database}`);
return list.map(config => {
if (isKnexInstance(config.connection)) {
log(`Received connection instance to database: ${config.connection.client.config.connection.database}`);

// TODO: Ask for `id` explicitly in for programmatic API,
// when Knex instance is passed directly.
// This implies a breaking change with the programmatic API.
return { connection, id: getConnectionId(getConfig(connection)) };
return { connection: config.connection, id: getConnectionId(config) };
}

log(`Creating a connection to database: ${connection.host}/${connection.database}`);

return { connection: createInstance(connection), id: getConnectionId(connection) };
return { connection: createInstance(config), id: getConnectionId(config) };
});
}
28 changes: 18 additions & 10 deletions test/util/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@ describe('CONFIG:', () => {
const connections = resolveConnectionsFromEnv();

expect(connections[0]).to.deep.equal({
client: 'mssql',
id: 'mydb',
host: 'localhost',
port: 1234,
user: 'user',
password: 'password',
database: 'database',
options: {
encrypt: false
client: 'mssql',
connection: {
host: 'localhost',
port: 1234,
user: 'user',
password: 'password',
database: 'database',
options: {
encrypt: false
}
}
});

Expand Down Expand Up @@ -95,10 +97,16 @@ describe('CONFIG:', () => {
it('should generate a config id using the host and database name if id does not exist.', () => {
expect(
getConnectionId({
host: 'localhost',
database: 'test'
connection: {
host: 'localhost',
database: 'test'
}
} as ConnectionConfig)
).to.equal('localhost/test');
});

it('should return empty string when id is not provided and connection string is passed.', () => {
expect(getConnectionId({ connection: 'someconnectionstring' } as ConnectionConfig)).to.equal('');
});
});
});

0 comments on commit 32f7536

Please sign in to comment.