Skip to content

Commit 44f6eec

Browse files
authored
Merge pull request #10 from leapfrogtechnology/refactor
Enable passing connection instance as well as connection config for synchronization
2 parents 3a97b16 + 04190df commit 44f6eec

File tree

14 files changed

+272
-103
lines changed

14 files changed

+272
-103
lines changed

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ sql:
130130
131131
## Usage
132132
133+
You can use sync-db as a CLI tool as well as within your scripts.
134+
135+
### CLI
136+
133137
When installed globally, you can just invoke the CLI directly.
134138
135139
```bash
@@ -141,7 +145,7 @@ For local installation you can trigger it with [`npx`](https://www.npmjs.com/pac
141145
$ npx sync-db
142146
```
143147

144-
### Using npm script
148+
#### Using npm script
145149

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

@@ -163,6 +167,53 @@ Or,
163167
$ npm run sync-db
164168
```
165169

170+
### Programmatic usage
171+
172+
Use sync-db's `synchronize` function within your `ts` scripts. You can use `synchronize` as follows:
173+
174+
```ts
175+
import * as Knex from 'knex';
176+
177+
import { loadConfig } from '@leapfrogtechnology/sync-db/lib/config';
178+
import { synchronize } from '@leapfrogtechnology/sync-db/lib/migrator';
179+
180+
(async () => {
181+
// Load sync-db.yml
182+
const config = await loadConfig();
183+
184+
const db = Knex({
185+
client: 'mssql',
186+
connection: {
187+
host: 'host',
188+
port: 'dbPort',
189+
user: 'userName',
190+
password: 'password',
191+
database: 'dbName'
192+
}
193+
});
194+
195+
// Rollback and create all db objects using config.
196+
await synchronize(config, db, { force: false });
197+
198+
// Perform other db operations
199+
// ...
200+
})();
201+
```
202+
203+
#### Caveat
204+
205+
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.**
206+
207+
```ts
208+
await db.transaction(async trx => {
209+
// Rollback and create all db objects using config.
210+
await synchronize(config, (trx as any), { force: false });
211+
212+
// Perform other db operations
213+
// ...
214+
});
215+
```
216+
166217
## Sample Projects
167218

168219
1. [Node MSSQL Sample (JavaScript)](examples/node-app-mssql)

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"contributors": [
2929
"Kabir Baidhya <kabirbaidhya@gmail.com>",
3030
"Saugat Acharya <mesaugat@gmail.com>",
31-
"Shraday Shakya <shradayshakya@gmail.com>"
31+
"Shraday Shakya <shradayshakya@gmail.com>",
32+
"Safal Raj Pandey <safal.pandey.sp@gmail.com>"
3233
],
3334
"scripts": {
3435
"posttest": "tslint -p test -t stylish",

src/Connection.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as Knex from 'knex';
2+
3+
import ConnectionConfig from './domain/ConnectionConfig';
4+
import RawBindingParams, { ValueMap } from './domain/RawBingingParams';
5+
6+
/**
7+
* Connection class wrapper with
8+
* both config and instance property.
9+
*/
10+
class Connection {
11+
/**
12+
* Creates new Connection object
13+
* using provided Knex connection instance.
14+
*
15+
* @param {Knex} db
16+
* @returns {Connection}
17+
*/
18+
public static withKnex(db: Knex): Connection {
19+
return new Connection({
20+
...db.client.config.connection,
21+
client: db.client.config.client,
22+
id: `${db.client.config.connection.host}/${db.client.config.connection.database}`
23+
});
24+
}
25+
26+
/**
27+
* Returns true if the provided object is a knex connection instance.
28+
*
29+
* @param {any} obj
30+
* @returns {boolean}
31+
*/
32+
public static isKnexInstance(obj: any): obj is Knex {
33+
return !!(obj.prototype && obj.prototype.constructor && obj.prototype.constructor.name === 'knex');
34+
}
35+
36+
private instance: Knex;
37+
private config: ConnectionConfig;
38+
39+
/**
40+
* Constructor.
41+
*
42+
* @param {ConnectionConfig} config
43+
*/
44+
constructor(config: ConnectionConfig) {
45+
this.config = config;
46+
this.instance = this.createInstance();
47+
}
48+
49+
/**
50+
* Creates a connection instance from
51+
* the provided database configuration.
52+
*
53+
* @param {ConnectionConfig} connectionConfig
54+
* @returns {Knex}
55+
*/
56+
public createInstance(): Knex {
57+
return Knex({
58+
client: this.config.client,
59+
connection: this.config
60+
});
61+
}
62+
63+
/**
64+
* Runs a query.
65+
*
66+
* @param {string} sql
67+
* @param {RawBindingParams | ValueMap} params
68+
* @returns {Promise<T[]>}
69+
*/
70+
public query<T>(sql: string, params: RawBindingParams | ValueMap = []): Promise<T[]> {
71+
return this.instance.raw(sql, params);
72+
}
73+
74+
/**
75+
* Runs a callback within transaction.
76+
*
77+
* @param {(trx: Connection) => any} callback
78+
* @returns {any}
79+
*/
80+
public transaction(callback: (trx: Connection) => any): Promise<any> {
81+
return this.instance.transaction(trx => callback(Connection.withKnex(trx)));
82+
}
83+
84+
/**
85+
* Returns connection config.
86+
*
87+
* @returns {ConnectionConfig}
88+
*/
89+
public getConfig(): ConnectionConfig {
90+
return this.config;
91+
}
92+
}
93+
94+
export default Connection;

src/config.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,25 @@ import { mergeDeepRight } from 'ramda';
44

55
import { log } from './logger';
66
import * as fs from './util/fs';
7-
import Connection from './domain/Connection';
8-
import Configuration from './domain/Configuration';
7+
import DbConfig from './domain/DbConfig';
8+
import SyncConfig from './domain/SyncConfig';
99
import ConnectionConfig from './domain/ConnectionConfig';
1010
import { DEFAULT_CONFIG, CONFIG_FILENAME, CONNECTIONS_FILENAME } from './constants';
1111

1212
/**
1313
* Load config yaml file.
1414
*
15-
* @returns {Promise<Configuration>}
15+
* @returns {Promise<SyncConfig>}
1616
*/
17-
export async function loadConfig(): Promise<Configuration> {
17+
export async function loadConfig(): Promise<SyncConfig> {
1818
log('Resolving sync config file.');
1919

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

2323
log('Resolved sync config file.');
2424

25-
const loaded = mergeDeepRight(DEFAULT_CONFIG, migrations) as Configuration;
25+
const loaded = mergeDeepRight(DEFAULT_CONFIG, migrations) as SyncConfig;
2626

2727
// TODO: Validate the loaded config.
2828

@@ -32,20 +32,19 @@ export async function loadConfig(): Promise<Configuration> {
3232
/**
3333
* Resolve database connections.
3434
*
35-
* @returns {Promise<Connection[]>}
35+
* @returns {Promise<ConnectionConfig[]>}
3636
*/
37-
export async function resolveConnections(): Promise<Connection[]> {
37+
export async function resolveConnections(): Promise<ConnectionConfig[]> {
3838
log('Resolving database connections.');
3939

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

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

4444
const loaded = await fs.read(filename);
45-
const { connections } = JSON.parse(loaded) as ConnectionConfig;
45+
const { connections } = JSON.parse(loaded) as DbConfig;
4646

4747
// TODO: Validate the connections received from file.
48-
4948
const result = connections.map(connection => ({
5049
...connection,
5150
id: connection.id || `${connection.host}/${connection.database}`

src/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import * as path from 'path';
44

5-
import Configuration from './domain/Configuration';
5+
import SyncConfig from './domain/SyncConfig';
66

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

10-
export const DEFAULT_CONFIG: Configuration = {
10+
export const DEFAULT_CONFIG: SyncConfig = {
1111
basePath: path.resolve(process.cwd(), 'src/sql'),
1212
sql: [],
1313
hooks: {

src/domain/Connection.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/domain/ConnectionConfig.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
import Connection from './Connection'
1+
import * as Knex from 'knex';
22

33
/**
4-
* Interface for sync-db connection file (connections.sync-db.json).
4+
* Database connection configuration.
55
*/
6-
interface ConnectionConfig {
7-
connections: Connection[];
8-
}
6+
type KnexConnections = Knex.ConnectionConfig
7+
& Knex.MariaSqlConnectionConfig
8+
& Knex.MySqlConnectionConfig
9+
& Knex.MsSqlConnectionConfig
10+
& Knex.SocketConnectionConfig
11+
& Knex.Sqlite3ConnectionConfig;
12+
13+
type ConnectionConfig = KnexConnections
14+
& {
15+
id?: string;
16+
client: string;
17+
};
918

1019
export default ConnectionConfig;

src/domain/DbConfig.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import ConnectionConfig from './ConnectionConfig';
2+
3+
/**
4+
* Interface for sync-db connection file (connections.sync-db.json).
5+
*/
6+
interface DbConfig {
7+
connections: ConnectionConfig[];
8+
}
9+
10+
export default DbConfig;

src/domain/RawBingingParams.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as Knex from 'knex';
2+
3+
type Value = Date | string | number | boolean | Date[] | string[] | number[] | boolean[] | Buffer | Knex.Raw;
4+
5+
type RawBindingParams = (
6+
| Date
7+
| Buffer
8+
| string
9+
| number
10+
| Date[]
11+
| boolean
12+
| Knex.Raw
13+
| string[]
14+
| number[]
15+
| boolean[]
16+
| Knex.QueryBuilder)[];
17+
18+
export interface ValueMap {
19+
[key: string]: Value | Knex.QueryBuilder;
20+
}
21+
22+
export default RawBindingParams;
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
2-
* Configuration.
2+
* Interface for synchronization configuration sycn-db.yml.
33
*/
4-
interface Configuration {
4+
interface SyncConfig {
55
basePath: string;
66
sql: string[];
77
hooks: {
@@ -10,4 +10,4 @@ interface Configuration {
1010
};
1111
}
1212

13-
export default Configuration;
13+
export default SyncConfig;

0 commit comments

Comments
 (0)