Skip to content

Commit

Permalink
Merge pull request #10 from leapfrogtechnology/refactor
Browse files Browse the repository at this point in the history
Enable passing connection instance as well as connection config for synchronization
  • Loading branch information
kabirbaidhya authored Aug 26, 2019
2 parents 3a97b16 + 04190df commit 44f6eec
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 103 deletions.
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ sql:
## Usage
You can use sync-db as a CLI tool as well as within your scripts.
### CLI
When installed globally, you can just invoke the CLI directly.
```bash
Expand All @@ -141,7 +145,7 @@ For local installation you can trigger it with [`npx`](https://www.npmjs.com/pac
$ npx sync-db
```

### Using npm script
#### Using npm script

You can also add a script into your project's `package.json` file like this:

Expand All @@ -163,6 +167,53 @@ Or,
$ npm run sync-db
```

### Programmatic usage

Use sync-db's `synchronize` function within your `ts` scripts. You can use `synchronize` as follows:

```ts
import * as Knex from 'knex';

import { loadConfig } from '@leapfrogtechnology/sync-db/lib/config';
import { synchronize } from '@leapfrogtechnology/sync-db/lib/migrator';

(async () => {
// Load sync-db.yml
const config = await loadConfig();

const db = Knex({
client: 'mssql',
connection: {
host: 'host',
port: 'dbPort',
user: 'userName',
password: 'password',
database: 'dbName'
}
});

// Rollback and create all db objects using config.
await synchronize(config, db, { force: false });

// Perform other db operations
// ...
})();
```

#### Caveat

Setup and Teardown steps aren't always run within a single transaction. **You need to specifically pass a trx object to make sure this happens.**

```ts
await db.transaction(async trx => {
// Rollback and create all db objects using config.
await synchronize(config, (trx as any), { force: false });

// Perform other db operations
// ...
});
```

## Sample Projects

1. [Node MSSQL Sample (JavaScript)](examples/node-app-mssql)
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"contributors": [
"Kabir Baidhya <kabirbaidhya@gmail.com>",
"Saugat Acharya <mesaugat@gmail.com>",
"Shraday Shakya <shradayshakya@gmail.com>"
"Shraday Shakya <shradayshakya@gmail.com>",
"Safal Raj Pandey <safal.pandey.sp@gmail.com>"
],
"scripts": {
"posttest": "tslint -p test -t stylish",
Expand Down
94 changes: 94 additions & 0 deletions src/Connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as Knex from 'knex';

import ConnectionConfig from './domain/ConnectionConfig';
import RawBindingParams, { ValueMap } from './domain/RawBingingParams';

/**
* Connection class wrapper with
* both config and instance property.
*/
class Connection {
/**
* Creates new Connection object
* using provided Knex connection instance.
*
* @param {Knex} db
* @returns {Connection}
*/
public static withKnex(db: Knex): Connection {
return new Connection({
...db.client.config.connection,
client: db.client.config.client,
id: `${db.client.config.connection.host}/${db.client.config.connection.database}`
});
}

/**
* Returns true if the provided object is a knex connection instance.
*
* @param {any} obj
* @returns {boolean}
*/
public static isKnexInstance(obj: any): obj is Knex {
return !!(obj.prototype && obj.prototype.constructor && obj.prototype.constructor.name === 'knex');
}

private instance: Knex;
private config: ConnectionConfig;

/**
* Constructor.
*
* @param {ConnectionConfig} config
*/
constructor(config: ConnectionConfig) {
this.config = config;
this.instance = this.createInstance();
}

/**
* Creates a connection instance from
* the provided database configuration.
*
* @param {ConnectionConfig} connectionConfig
* @returns {Knex}
*/
public createInstance(): Knex {
return Knex({
client: this.config.client,
connection: this.config
});
}

/**
* Runs a query.
*
* @param {string} sql
* @param {RawBindingParams | ValueMap} params
* @returns {Promise<T[]>}
*/
public query<T>(sql: string, params: RawBindingParams | ValueMap = []): Promise<T[]> {
return this.instance.raw(sql, params);
}

/**
* Runs a callback within transaction.
*
* @param {(trx: Connection) => any} callback
* @returns {any}
*/
public transaction(callback: (trx: Connection) => any): Promise<any> {
return this.instance.transaction(trx => callback(Connection.withKnex(trx)));
}

/**
* Returns connection config.
*
* @returns {ConnectionConfig}
*/
public getConfig(): ConnectionConfig {
return this.config;
}
}

export default Connection;
19 changes: 9 additions & 10 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@ import { mergeDeepRight } from 'ramda';

import { log } from './logger';
import * as fs from './util/fs';
import Connection from './domain/Connection';
import Configuration from './domain/Configuration';
import DbConfig from './domain/DbConfig';
import SyncConfig from './domain/SyncConfig';
import ConnectionConfig from './domain/ConnectionConfig';
import { DEFAULT_CONFIG, CONFIG_FILENAME, CONNECTIONS_FILENAME } from './constants';

/**
* Load config yaml file.
*
* @returns {Promise<Configuration>}
* @returns {Promise<SyncConfig>}
*/
export async function loadConfig(): Promise<Configuration> {
export async function loadConfig(): Promise<SyncConfig> {
log('Resolving sync config file.');

const filename = path.resolve(process.cwd(), CONFIG_FILENAME);
const migrations = (await yaml.load(filename)) as Configuration;
const migrations = await yaml.load(filename) as SyncConfig;

log('Resolved sync config file.');

const loaded = mergeDeepRight(DEFAULT_CONFIG, migrations) as Configuration;
const loaded = mergeDeepRight(DEFAULT_CONFIG, migrations) as SyncConfig;

// TODO: Validate the loaded config.

Expand All @@ -32,20 +32,19 @@ export async function loadConfig(): Promise<Configuration> {
/**
* Resolve database connections.
*
* @returns {Promise<Connection[]>}
* @returns {Promise<ConnectionConfig[]>}
*/
export async function resolveConnections(): Promise<Connection[]> {
export async function resolveConnections(): Promise<ConnectionConfig[]> {
log('Resolving database connections.');

const filename = path.resolve(process.cwd(), CONNECTIONS_FILENAME);

log('Resolving file: %s', filename);

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

// TODO: Validate the connections received from file.

const result = connections.map(connection => ({
...connection,
id: connection.id || `${connection.host}/${connection.database}`
Expand Down
4 changes: 2 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import * as path from 'path';

import Configuration from './domain/Configuration';
import SyncConfig from './domain/SyncConfig';

export const CONFIG_FILENAME = 'sync-db.yml';
export const CONNECTIONS_FILENAME = 'connections.sync-db.json';

export const DEFAULT_CONFIG: Configuration = {
export const DEFAULT_CONFIG: SyncConfig = {
basePath: path.resolve(process.cwd(), 'src/sql'),
sql: [],
hooks: {
Expand Down
17 changes: 0 additions & 17 deletions src/domain/Connection.ts

This file was deleted.

19 changes: 14 additions & 5 deletions src/domain/ConnectionConfig.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import Connection from './Connection'
import * as Knex from 'knex';

/**
* Interface for sync-db connection file (connections.sync-db.json).
* Database connection configuration.
*/
interface ConnectionConfig {
connections: Connection[];
}
type KnexConnections = Knex.ConnectionConfig
& Knex.MariaSqlConnectionConfig
& Knex.MySqlConnectionConfig
& Knex.MsSqlConnectionConfig
& Knex.SocketConnectionConfig
& Knex.Sqlite3ConnectionConfig;

type ConnectionConfig = KnexConnections
& {
id?: string;
client: string;
};

export default ConnectionConfig;
10 changes: 10 additions & 0 deletions src/domain/DbConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import ConnectionConfig from './ConnectionConfig';

/**
* Interface for sync-db connection file (connections.sync-db.json).
*/
interface DbConfig {
connections: ConnectionConfig[];
}

export default DbConfig;
22 changes: 22 additions & 0 deletions src/domain/RawBingingParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as Knex from 'knex';

type Value = Date | string | number | boolean | Date[] | string[] | number[] | boolean[] | Buffer | Knex.Raw;

type RawBindingParams = (
| Date
| Buffer
| string
| number
| Date[]
| boolean
| Knex.Raw
| string[]
| number[]
| boolean[]
| Knex.QueryBuilder)[];

export interface ValueMap {
[key: string]: Value | Knex.QueryBuilder;
}

export default RawBindingParams;
6 changes: 3 additions & 3 deletions src/domain/Configuration.ts → src/domain/SyncConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Configuration.
* Interface for synchronization configuration sycn-db.yml.
*/
interface Configuration {
interface SyncConfig {
basePath: string;
sql: string[];
hooks: {
Expand All @@ -10,4 +10,4 @@ interface Configuration {
};
}

export default Configuration;
export default SyncConfig;
4 changes: 2 additions & 2 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as debug from 'debug';

import Connection from './domain/Connection';
import Connection from './Connection';

export const SYNC_DB = 'sync-db';
export const log = debug(SYNC_DB);
export const dbLogger = (db: Connection) => log.extend(db.id || db.database);
export const dbLogger = (conn: Connection) => log.extend(conn.getConfig().id || conn.getConfig().database);
Loading

0 comments on commit 44f6eec

Please sign in to comment.