ffc-pay-etl-framework 1.4.1-alpha.5 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/database_connections/index.js +7 -0
- package/app/database_connections/postgresDatabaseConnection.js +47 -0
- package/app/database_connections/providedConnection.js +19 -0
- package/app/destinations/README.md +107 -0
- package/app/destinations/consoleDestination.js +38 -0
- package/app/destinations/csvFileDestination.js +75 -0
- package/app/destinations/index.js +12 -0
- package/app/destinations/postgresDestination.js +139 -0
- package/app/destinations/sqlFileDestination.js +81 -0
- package/app/lib/index.js +106 -0
- package/app/lib/rowMetaData.js +24 -0
- package/app/loaders/csvloader.js +58 -0
- package/app/loaders/index.js +5 -0
- package/app/misc/README.md +55 -0
- package/app/misc/index.js +0 -0
- package/app/misc/postgresSQLTask.js +71 -0
- package/app/transformers/README.md +102 -0
- package/app/transformers/fakerTransformer.js +48 -0
- package/app/transformers/index.js +9 -0
- package/app/transformers/stringReplaceTransformer.js +35 -0
- package/app/transformers/toUpperCaseTransformer.js +27 -0
- package/app/validators/README.md +81 -0
- package/app/validators/index.js +9 -0
- package/app/validators/multiToolValidator.js +49 -0
- package/app/validators/requiredValidator.js +35 -0
- package/app/validators/uniqueValidator.js +41 -0
- package/examples/additional-tasks.js +127 -0
- package/examples/csv-faker.js +30 -0
- package/examples/csv-find-replace.js +97 -0
- package/examples/csv-multi-tool-validator.js +38 -0
- package/examples/csv-required-validator.js +27 -0
- package/examples/csv-to-csv.js +24 -0
- package/examples/csv-to-postgres.js +36 -0
- package/examples/csv-to-sqlfile.js +31 -0
- package/examples/csv-to-upper-case.js +25 -0
- package/examples/csv-unique-validator.js +27 -0
- package/examples/index.js +26 -0
- package/examples/sql-return-values.js +37 -0
- package/package.json +25 -41
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const DEFAULT_PORT = 5432
|
|
2
|
+
const { Sequelize } = require('sequelize')
|
|
3
|
+
const debug = require('debug')('connection')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} options
|
|
8
|
+
* @param {Object} options.connectionname
|
|
9
|
+
* @param {Object} options.username
|
|
10
|
+
* @param {Object} options.password
|
|
11
|
+
* @param {Object} options.database
|
|
12
|
+
* @param {Object} options.host
|
|
13
|
+
* @param {Object} options.port
|
|
14
|
+
* @returns Connection
|
|
15
|
+
*/
|
|
16
|
+
async function PostgresDatabaseConnection (options) {
|
|
17
|
+
const connectionname = options.connectionname
|
|
18
|
+
const username = options.username
|
|
19
|
+
const password = options.password
|
|
20
|
+
const database = options.database
|
|
21
|
+
const host = options.host
|
|
22
|
+
const port = options.port || DEFAULT_PORT
|
|
23
|
+
|
|
24
|
+
const sequelize = new Sequelize(database, username, password, {
|
|
25
|
+
host,
|
|
26
|
+
port,
|
|
27
|
+
dialect: 'postgres',
|
|
28
|
+
logging: false
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await sequelize.authenticate()
|
|
33
|
+
debug('sequelize.authenticate succeeded')
|
|
34
|
+
return {
|
|
35
|
+
name: connectionname,
|
|
36
|
+
db: sequelize
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
debug('sequelize.authenticate failed')
|
|
40
|
+
debug(e)
|
|
41
|
+
throw e
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
PostgresDatabaseConnection
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {Object} options
|
|
4
|
+
* @param {Object} options.connectionname
|
|
5
|
+
* @param {Object} options.sequelize
|
|
6
|
+
* @returns Connection
|
|
7
|
+
*/
|
|
8
|
+
async function ProvidedConnection (options) {
|
|
9
|
+
const connectionname = options.connectionname
|
|
10
|
+
const sequelize = options.sequelize
|
|
11
|
+
return {
|
|
12
|
+
name: connectionname,
|
|
13
|
+
db: sequelize
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
ProvidedConnection
|
|
19
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Destinations
|
|
2
|
+
|
|
3
|
+
Destinations are expected to write out to an output. This could be a file, a database, an endpoint, whatever.
|
|
4
|
+
|
|
5
|
+
Currently supported destinations are :
|
|
6
|
+
|
|
7
|
+
- CSVFileDestination
|
|
8
|
+
- PostgresDestination
|
|
9
|
+
- SQLFileDestination
|
|
10
|
+
|
|
11
|
+
## CSVFileDestination
|
|
12
|
+
|
|
13
|
+
### Options
|
|
14
|
+
|
|
15
|
+
| option | description |
|
|
16
|
+
| -------------- | ------------------------------------------------------------- |
|
|
17
|
+
| fileName | absolute path to the csv file to be written |
|
|
18
|
+
| headers | treat the first line of the file as column header information |
|
|
19
|
+
| includeErrors | write out any rows that contain errors |
|
|
20
|
+
| quotationMarks | wrap values in quotation marks |
|
|
21
|
+
|
|
22
|
+
### Usage
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
CSVFileDestination({
|
|
26
|
+
fileName: "...",
|
|
27
|
+
headers: true,
|
|
28
|
+
includeErrors: false,
|
|
29
|
+
quotationMarks: true,
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Example
|
|
34
|
+
|
|
35
|
+
examples/csv-to-csv.js
|
|
36
|
+
|
|
37
|
+
## PostgresDestination
|
|
38
|
+
|
|
39
|
+
### Options
|
|
40
|
+
|
|
41
|
+
| option | description |
|
|
42
|
+
| ------------- | ------------------------------------------------------ |
|
|
43
|
+
| table | the name of the table to be inserted into |
|
|
44
|
+
| username | the username for the database connection |
|
|
45
|
+
| password | the password for the database connection |
|
|
46
|
+
| database | the name of the database for the connection |
|
|
47
|
+
| host | the host name for the database connection |
|
|
48
|
+
| port | the port for the database connection. Defaults to 5432 |
|
|
49
|
+
| includeErrors | write out any rows that contain errors |
|
|
50
|
+
| mapping | field mappings from source to target |
|
|
51
|
+
|
|
52
|
+
### Usage
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
PostgresDestination({
|
|
56
|
+
username: "postgres",
|
|
57
|
+
password: "ppp",
|
|
58
|
+
database: "etl_db",
|
|
59
|
+
host: "postgres",
|
|
60
|
+
table: "target",
|
|
61
|
+
includeErrors: false,
|
|
62
|
+
mapping: [
|
|
63
|
+
{
|
|
64
|
+
column: "column1",
|
|
65
|
+
targetColumn: "target_column1",
|
|
66
|
+
targetType: "varchar",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Example
|
|
73
|
+
|
|
74
|
+
examples/csv-to-postgres.js
|
|
75
|
+
|
|
76
|
+
## SQLFileDestination
|
|
77
|
+
|
|
78
|
+
### options
|
|
79
|
+
|
|
80
|
+
| option | description |
|
|
81
|
+
| -------- | ------------------------------------------- |
|
|
82
|
+
| fileName | absolute path to the sql file to be written |
|
|
83
|
+
| mode | insert or update mode |
|
|
84
|
+
| table | the table for the statements |
|
|
85
|
+
| mapping | field mappings from source to target |
|
|
86
|
+
|
|
87
|
+
### Usage
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
SQLFileDestination({
|
|
91
|
+
filename: "...",
|
|
92
|
+
mode: SQL_MODE.INSERT_MODE,
|
|
93
|
+
table: "target",
|
|
94
|
+
includeErrors: false,
|
|
95
|
+
mapping: [
|
|
96
|
+
{
|
|
97
|
+
column: "column1",
|
|
98
|
+
targetColumn: "target_column1",
|
|
99
|
+
targetType: "varchar",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Example
|
|
106
|
+
|
|
107
|
+
examples/csv-to-sqlfile.js
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { Writable } = require('node:stream')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {Object} options
|
|
6
|
+
* @param {String} options.includeErrors
|
|
7
|
+
* @returns Writable
|
|
8
|
+
*/
|
|
9
|
+
function ConsoleDestination (options) {
|
|
10
|
+
const includeErrors = options.includeErrors
|
|
11
|
+
const writable = new Writable({
|
|
12
|
+
objectMode: true,
|
|
13
|
+
write (chunk, _, callback) {
|
|
14
|
+
if (chunk.errors.length === 0 || includeErrors) { console.log(chunk) }
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
this.tasks?.forEach(task => task.write(chunk))
|
|
17
|
+
callback()
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
Object.assign(writable, {
|
|
22
|
+
setConnection: function (connection) {
|
|
23
|
+
this.connection = connection
|
|
24
|
+
}.bind(writable),
|
|
25
|
+
getConnectionName: function () {
|
|
26
|
+
return this.connection?.name
|
|
27
|
+
}.bind(writable),
|
|
28
|
+
setTasks: function (tasks) {
|
|
29
|
+
this.tasks = tasks
|
|
30
|
+
}.bind(writable)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return writable
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
ConsoleDestination
|
|
38
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const EventEmitter = require('node:events')
|
|
2
|
+
const util = require('node:util')
|
|
3
|
+
const { Writable } = require('node:stream')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options
|
|
9
|
+
* @param {String} options.fileName
|
|
10
|
+
* @param {Boolean} options.headers
|
|
11
|
+
* @param {Boolean} options.includeErrors
|
|
12
|
+
* @param {Boolean} options.quotationMarks
|
|
13
|
+
* @returns Writable
|
|
14
|
+
*/
|
|
15
|
+
function CSVFileDestination (options) {
|
|
16
|
+
EventEmitter.call(this)
|
|
17
|
+
let lastChunk
|
|
18
|
+
const fileName = options.fileName
|
|
19
|
+
const headers = options.headers
|
|
20
|
+
const includeErrors = options.includeErrors
|
|
21
|
+
const quotationMarks = options.quotationMarks
|
|
22
|
+
let fileHandle
|
|
23
|
+
fs.open(fileName, 'w+', (_error, fd) => {
|
|
24
|
+
fileHandle = fd
|
|
25
|
+
})
|
|
26
|
+
let headersWritten = false
|
|
27
|
+
const writable = new Writable({
|
|
28
|
+
objectMode: true,
|
|
29
|
+
write (chunk, _, callback) {
|
|
30
|
+
if (!headersWritten && headers) {
|
|
31
|
+
if (quotationMarks) {
|
|
32
|
+
fs.writeFileSync(fileHandle, `${chunk._columns.map(c => `"${c}"`).join(',')}\n`)
|
|
33
|
+
} else {
|
|
34
|
+
fs.writeFileSync(fileHandle, `${chunk._columns.join(',')}\n`)
|
|
35
|
+
}
|
|
36
|
+
headersWritten = true
|
|
37
|
+
}
|
|
38
|
+
if (chunk.errors.length === 0 || includeErrors) {
|
|
39
|
+
if (quotationMarks) {
|
|
40
|
+
fs.writeFileSync(fileHandle, `${chunk.map(c => `"${c}"`).join(',')}\n`)
|
|
41
|
+
} else {
|
|
42
|
+
fs.writeFileSync(fileHandle, `${chunk.map(c => `"${c}"`).join(',')}\n`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
this.tasks?.forEach(task => task.write(chunk))
|
|
47
|
+
lastChunk = chunk
|
|
48
|
+
callback()
|
|
49
|
+
},
|
|
50
|
+
final (callback) {
|
|
51
|
+
this.emit('result', lastChunk)
|
|
52
|
+
callback()
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
Object.assign(writable, {
|
|
57
|
+
setConnection: function (connection) {
|
|
58
|
+
this.connection = connection
|
|
59
|
+
}.bind(writable),
|
|
60
|
+
getConnectionName: function () {
|
|
61
|
+
return this.connection?.name
|
|
62
|
+
}.bind(writable),
|
|
63
|
+
setTasks: function (tasks) {
|
|
64
|
+
this.tasks = tasks
|
|
65
|
+
}.bind(writable)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
return writable
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
util.inherits(CSVFileDestination, EventEmitter)
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
CSVFileDestination
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { ConsoleDestination } = require('./consoleDestination')
|
|
2
|
+
const { SQLFileDestination, SQL_MODE } = require('./sqlFileDestination')
|
|
3
|
+
const { CSVFileDestination } = require('./csvFileDestination')
|
|
4
|
+
const { PostgresDestination } = require('./postgresDestination')
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
ConsoleDestination,
|
|
8
|
+
SQLFileDestination,
|
|
9
|
+
SQL_MODE,
|
|
10
|
+
CSVFileDestination,
|
|
11
|
+
PostgresDestination
|
|
12
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const EventEmitter = require('node:events')
|
|
2
|
+
const { Transform } = require('node:stream')
|
|
3
|
+
const debug = require('debug')('destination')
|
|
4
|
+
|
|
5
|
+
function isKeyWord (column) {
|
|
6
|
+
return ['USER'].includes(column.toUpperCase())
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getMappingForColumn (mapping, column) {
|
|
10
|
+
if (mapping.length === 0) { return {} }
|
|
11
|
+
const [map] = mapping.filter(m => m.column === column)
|
|
12
|
+
return map
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hasReturningColumns (mapping) {
|
|
16
|
+
return mapping.filter(m => m.returning === true).length > 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getReturningColumns (mapping) {
|
|
20
|
+
return mapping.filter(m => m.returning === true).map(m => m?.targetColumn)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeInsertStatement (columnMapping, table, chunk, schema, ignoredColumns = []) {
|
|
24
|
+
const filteredColumns = chunk._columns.filter(column => {
|
|
25
|
+
const mapping = getMappingForColumn(columnMapping, column)
|
|
26
|
+
const targetColumn = mapping?.targetColumn ? mapping?.targetColumn : mapping?.column
|
|
27
|
+
return !ignoredColumns.includes(targetColumn)
|
|
28
|
+
})
|
|
29
|
+
let statement = `INSERT INTO ${schema ?? 'public'}."${table}" (${filteredColumns.map(column => {
|
|
30
|
+
const mapping = getMappingForColumn(columnMapping, column)
|
|
31
|
+
return mapping?.targetColumn ? `"${mapping.targetColumn}"` : `"${mapping?.column}"`
|
|
32
|
+
})
|
|
33
|
+
.join(',')}) VALUES (${filteredColumns.map(column => {
|
|
34
|
+
const index = chunk._columns.indexOf(column)
|
|
35
|
+
const mapping = getMappingForColumn(columnMapping, column)
|
|
36
|
+
if (mapping?.targetType === 'number' && (isNaN(chunk[index]) || chunk[index] === '')) {
|
|
37
|
+
debug('Source data is not a number.')
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
40
|
+
if (mapping?.targetType === 'varchar' || mapping?.targetType === 'char') {
|
|
41
|
+
return `'${chunk[index]}'`
|
|
42
|
+
}
|
|
43
|
+
if (mapping?.targetType === 'date') {
|
|
44
|
+
if (!chunk[index]) {
|
|
45
|
+
return '\'\''
|
|
46
|
+
}
|
|
47
|
+
return `to_timestamp('${chunk[index]}','${mapping?.format}')`
|
|
48
|
+
}
|
|
49
|
+
return chunk[index] ? chunk[index] : 'null'
|
|
50
|
+
})})`
|
|
51
|
+
if (hasReturningColumns(columnMapping)) {
|
|
52
|
+
statement = statement + ` RETURNING ${getReturningColumns(columnMapping).join(',')}`
|
|
53
|
+
}
|
|
54
|
+
return statement
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @param {Object} options
|
|
60
|
+
* @param {Object} options.table
|
|
61
|
+
* @param {Object} options.connectionname
|
|
62
|
+
* @param {Object} options.mapping
|
|
63
|
+
* @param {Object} options.includeErrors
|
|
64
|
+
* @param {String} [options.schema]
|
|
65
|
+
* @param {Array<String>} [options.ignoredColumns]
|
|
66
|
+
* @returns Transform
|
|
67
|
+
*/
|
|
68
|
+
function PostgresDestination (options) {
|
|
69
|
+
EventEmitter.call(this)
|
|
70
|
+
const table = options.table
|
|
71
|
+
const connectionname = options.connectionname
|
|
72
|
+
const mapping = options.mapping
|
|
73
|
+
const schema = options.schema
|
|
74
|
+
const ignoredColumns = options.ignoredColumns ?? []
|
|
75
|
+
let lastChunk
|
|
76
|
+
|
|
77
|
+
const transform = new Transform({
|
|
78
|
+
objectMode: true,
|
|
79
|
+
emitClose: true,
|
|
80
|
+
construct (callback) {
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
this.connectionname = connectionname
|
|
83
|
+
callback()
|
|
84
|
+
},
|
|
85
|
+
write (chunk, _, callback) {
|
|
86
|
+
let insertStatement
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
if (chunk.errors.length === 0 || options.includeErrors) {
|
|
89
|
+
insertStatement = writeInsertStatement(mapping, table, chunk, schema, ignoredColumns)
|
|
90
|
+
debug('Insert statement: [%s]', insertStatement)
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
this.connection.db.query(insertStatement)
|
|
93
|
+
.then(result => {
|
|
94
|
+
debug('result %o', result)
|
|
95
|
+
chunk._result = result
|
|
96
|
+
lastChunk = chunk
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
this.tasks?.forEach(task => task.write(chunk))
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
callback(null, chunk)
|
|
101
|
+
}).catch(error => {
|
|
102
|
+
debug('error %o', error)
|
|
103
|
+
chunk.errors.push(error)
|
|
104
|
+
lastChunk = chunk
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
callback(error, chunk)
|
|
107
|
+
})
|
|
108
|
+
} else {
|
|
109
|
+
debug('Chunk has errors %o', chunk)
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
final (callback) {
|
|
113
|
+
this.emit('result', lastChunk)
|
|
114
|
+
callback()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
Object.assign(Transform.prototype, {
|
|
118
|
+
type: 'PostgresDestination',
|
|
119
|
+
setConnection: function (connection) {
|
|
120
|
+
this.connection = connection
|
|
121
|
+
}.bind(transform),
|
|
122
|
+
getConnectionName: function () {
|
|
123
|
+
return this.connection?.name
|
|
124
|
+
}.bind(transform),
|
|
125
|
+
setTasks: function (tasks) {
|
|
126
|
+
this.tasks = tasks
|
|
127
|
+
}.bind(transform)
|
|
128
|
+
})
|
|
129
|
+
return transform
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
PostgresDestination,
|
|
134
|
+
writeInsertStatement,
|
|
135
|
+
isKeyWord,
|
|
136
|
+
getMappingForColumn,
|
|
137
|
+
hasReturningColumns,
|
|
138
|
+
getReturningColumns
|
|
139
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { Writable } = require('node:stream')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @enum {number}
|
|
6
|
+
*/
|
|
7
|
+
const SQL_MODE = {
|
|
8
|
+
INSERT_MODE: 1,
|
|
9
|
+
UPDATE_MODE: 2
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {String} options.fileName
|
|
16
|
+
* @param {SQL_MODE} options.mode
|
|
17
|
+
* @param {String} options.table
|
|
18
|
+
* @param {Array} options.mapping
|
|
19
|
+
* @param {Boolean} options.includeErrors
|
|
20
|
+
* @returns Transform
|
|
21
|
+
*/
|
|
22
|
+
function SQLFileDestination (options) {
|
|
23
|
+
const fileName = options.fileName
|
|
24
|
+
const sqlMode = options.mode
|
|
25
|
+
const table = options.table
|
|
26
|
+
const mapping = options.mapping
|
|
27
|
+
const includeErrors = options.includeErrors
|
|
28
|
+
|
|
29
|
+
function writeInsertStatement (chunk) {
|
|
30
|
+
const statement = `INSERT INTO ${table} (${mapping.map(m => m.targetColumn)
|
|
31
|
+
.join(',')}) VALUES (${mapping.map((m) => {
|
|
32
|
+
const srcColumnIndex = chunk._columns.indexOf(m.column)
|
|
33
|
+
if (m.targetType === 'string') { return `'${chunk[srcColumnIndex]}'` }
|
|
34
|
+
return chunk[srcColumnIndex]
|
|
35
|
+
})});\n`
|
|
36
|
+
fs.writeFileSync(fileName, statement, {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
flag: 'a+'
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
const writable = new Writable({
|
|
42
|
+
objectMode: true,
|
|
43
|
+
write (chunk, _, callback) {
|
|
44
|
+
if (chunk.errors.length === 0) {
|
|
45
|
+
if (sqlMode === SQL_MODE.INSERT_MODE) {
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
if (chunk.errors.length === 0 | includeErrors) {
|
|
48
|
+
writeInsertStatement(chunk)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// TODO - Disabled because Sonar
|
|
53
|
+
// } else if (sqlMode === SQL_MODE.UPDATE_MODE){
|
|
54
|
+
// // @ts-ignore
|
|
55
|
+
// if(chunk.errors.length === 0 | includeErrors){
|
|
56
|
+
// writeUpdateStatement(chunk)
|
|
57
|
+
// }
|
|
58
|
+
// }
|
|
59
|
+
callback()
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
Object.assign(writable, {
|
|
64
|
+
setConnection: function (connection) {
|
|
65
|
+
this.connection = connection
|
|
66
|
+
}.bind(writable),
|
|
67
|
+
getConnectionName: function () {
|
|
68
|
+
return this.connection?.name
|
|
69
|
+
}.bind(writable),
|
|
70
|
+
setTasks: function (tasks) {
|
|
71
|
+
this.tasks = tasks
|
|
72
|
+
}.bind(writable)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return writable
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
SQLFileDestination,
|
|
80
|
+
SQL_MODE
|
|
81
|
+
}
|
package/app/lib/index.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
const EventEmitter = require('node:events')
|
|
3
|
+
const util = require('node:util')
|
|
4
|
+
const { RowMetaData } = require('./rowMetaData')
|
|
5
|
+
const { compose } = require('node:stream')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} Etl
|
|
9
|
+
* @function loader
|
|
10
|
+
* @function pump
|
|
11
|
+
* @function connection
|
|
12
|
+
* @function validator
|
|
13
|
+
* @function destination
|
|
14
|
+
* @function transform
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @returns Etl
|
|
20
|
+
*/
|
|
21
|
+
function Etl () {
|
|
22
|
+
EventEmitter.call(this)
|
|
23
|
+
const self = this
|
|
24
|
+
self.store = []
|
|
25
|
+
self.beforeETLList = []
|
|
26
|
+
self.connectionList = []
|
|
27
|
+
self.validatorList = []
|
|
28
|
+
self.transformationList = []
|
|
29
|
+
self.destinationList = []
|
|
30
|
+
|
|
31
|
+
this.loader = (loader) => {
|
|
32
|
+
self.loader = loader
|
|
33
|
+
return self
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.pump = () => {
|
|
37
|
+
this.beforeETLList.forEach(task => {
|
|
38
|
+
task.write({})
|
|
39
|
+
})
|
|
40
|
+
this.loader
|
|
41
|
+
.pump(this.loader)
|
|
42
|
+
.pipe(
|
|
43
|
+
compose(
|
|
44
|
+
RowMetaData(),
|
|
45
|
+
...self.validatorList,
|
|
46
|
+
...self.transformationList,
|
|
47
|
+
...self.destinationList.map(dl => dl.on('result', (data) => self.emit('result', data)))
|
|
48
|
+
)
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
)
|
|
51
|
+
.on('error', (err) => self.emit('error', err))
|
|
52
|
+
return self
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.beforeETL = (pipelineTask) => {
|
|
56
|
+
const connectionname = pipelineTask.getConnectionName()
|
|
57
|
+
const connection = this.connectionList.filter(c => c.name === connectionname)[0]
|
|
58
|
+
if (!connection) {
|
|
59
|
+
throw new Error(`Connection with name ${connectionname} not found`)
|
|
60
|
+
}
|
|
61
|
+
pipelineTask.setConnection(connection)
|
|
62
|
+
pipelineTask.setETL(self)
|
|
63
|
+
self.beforeETLList.push(pipelineTask)
|
|
64
|
+
return self
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.connection = (connection) => {
|
|
68
|
+
self.connectionList.push(connection)
|
|
69
|
+
return self
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.validator = (validator) => {
|
|
73
|
+
self.validatorList.push(validator)
|
|
74
|
+
return self
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.destination = (destination, ...tasks) => {
|
|
78
|
+
const connectionname = destination.getConnectionName()
|
|
79
|
+
const connection = this.connectionList.filter(c => c.name === connectionname)[0]
|
|
80
|
+
if (!connection && destination.type === 'PostgresDestination') {
|
|
81
|
+
throw new Error(`No connection could be found with name ${connectionname}`)
|
|
82
|
+
} else {
|
|
83
|
+
destination.setConnection(connection)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (tasks) {
|
|
87
|
+
for (const task of tasks) {
|
|
88
|
+
task.setETL(self)
|
|
89
|
+
}
|
|
90
|
+
destination.setTasks(tasks)
|
|
91
|
+
}
|
|
92
|
+
self.destinationList.push(destination)
|
|
93
|
+
return self
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.transform = (transform) => {
|
|
97
|
+
self.transformationList.push(transform)
|
|
98
|
+
return self
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
util.inherits(Etl, EventEmitter)
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
Etl
|
|
106
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
const { Transform } = require('stream')
|
|
3
|
+
|
|
4
|
+
function RowMetaData (options) {
|
|
5
|
+
return new Transform({
|
|
6
|
+
readableObjectMode: true,
|
|
7
|
+
writableObjectMode: true,
|
|
8
|
+
decodeStrings: false,
|
|
9
|
+
construct (callback) {
|
|
10
|
+
this.rowId = 0
|
|
11
|
+
callback()
|
|
12
|
+
},
|
|
13
|
+
transform (chunk, _, callback) {
|
|
14
|
+
chunk['_rowId'] = this.rowId
|
|
15
|
+
chunk['errors'] = []
|
|
16
|
+
this.rowId += 1
|
|
17
|
+
callback(null, chunk)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
RowMetaData
|
|
24
|
+
}
|