ffc-pay-etl-framework 1.4.0 → 1.4.1-alpha.13

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.
Files changed (40) hide show
  1. package/app/database_connections/index.js +7 -0
  2. package/app/database_connections/postgresDatabaseConnection.js +47 -0
  3. package/app/database_connections/providedConnection.js +19 -0
  4. package/app/destinations/README.md +107 -0
  5. package/app/destinations/consoleDestination.js +38 -0
  6. package/app/destinations/csvFileDestination.js +75 -0
  7. package/app/destinations/index.js +12 -0
  8. package/app/destinations/postgresDestination.js +139 -0
  9. package/app/destinations/sqlFileDestination.js +81 -0
  10. package/app/lib/index.js +106 -0
  11. package/app/lib/rowMetaData.js +24 -0
  12. package/app/loaders/csvloader.js +58 -0
  13. package/app/loaders/index.js +5 -0
  14. package/app/misc/README.md +55 -0
  15. package/app/misc/index.js +0 -0
  16. package/app/misc/postgresSQLTask.js +71 -0
  17. package/app/transformers/README.md +102 -0
  18. package/app/transformers/fakerTransformer.js +48 -0
  19. package/app/transformers/index.js +9 -0
  20. package/app/transformers/stringReplaceTransformer.js +35 -0
  21. package/app/transformers/toUpperCaseTransformer.js +27 -0
  22. package/app/validators/README.md +81 -0
  23. package/app/validators/index.js +9 -0
  24. package/app/validators/multiToolValidator.js +49 -0
  25. package/app/validators/requiredValidator.js +35 -0
  26. package/app/validators/uniqueValidator.js +41 -0
  27. package/examples/additional-tasks.js +127 -0
  28. package/examples/csv-faker.js +30 -0
  29. package/examples/csv-find-replace.js +97 -0
  30. package/examples/csv-multi-tool-validator.js +38 -0
  31. package/examples/csv-required-validator.js +27 -0
  32. package/examples/csv-to-csv.js +24 -0
  33. package/examples/csv-to-postgres.js +36 -0
  34. package/examples/csv-to-sqlfile.js +31 -0
  35. package/examples/csv-to-upper-case.js +25 -0
  36. package/examples/csv-unique-validator.js +27 -0
  37. package/examples/index.js +26 -0
  38. package/examples/sql-return-values.js +37 -0
  39. package/index.js +15 -0
  40. package/package.json +28 -44
@@ -0,0 +1,58 @@
1
+ // @ts-nocheck
2
+ const fs = require('fs')
3
+ const { Transform } = require('stream')
4
+ const { parse } = require('csv-parse')
5
+
6
+ /**
7
+ *
8
+ * @param {Object} options
9
+ * @param {String} [options.path]
10
+ * @param {ReadableStream} [options.stream]
11
+ * @param {Number} [options.startingLine]
12
+ * @param {Boolean} [options.relax]
13
+ * @returns StreamReader
14
+ */
15
+ function CSVLoader (options) {
16
+ let csvLoader
17
+ if (options.path) {
18
+ csvLoader = fs.createReadStream(options.path)
19
+ } else {
20
+ csvLoader = options.stream
21
+ }
22
+ let lineCount = 1
23
+ const fromLine = options.startingLine ?? 2
24
+ const relaxQuotes = options.relax ?? false
25
+ csvLoader._columns = options.columns
26
+ csvLoader.pump = (csvLoader) => {
27
+ const parser = parse({ delimiter: ',', from_line: fromLine, relax_quotes: relaxQuotes })
28
+ const transformer = new Transform({
29
+ readableObjectMode: true,
30
+ writableObjectMode: true,
31
+ emitClose: true,
32
+ transform (chunk, _, callback) {
33
+ chunk['_columns'] = options.columns
34
+ chunk['_linecount'] = lineCount
35
+ lineCount += 1
36
+
37
+ // remove non-printable characters
38
+ options.columns.forEach((_column, index) => {
39
+ if (chunk[index]) {
40
+ // eslint-disable-next-line no-control-regex
41
+ chunk[index] = chunk[index].replace(/[\x00-\x1F\x7F-\x9F]/g, '')
42
+ }
43
+ })
44
+
45
+ callback(null, chunk)
46
+ }
47
+ })
48
+ return csvLoader
49
+ .pipe(parser)
50
+ .pipe(transformer)
51
+ }
52
+
53
+ return csvLoader
54
+ }
55
+
56
+ module.exports = {
57
+ CSVLoader
58
+ }
@@ -0,0 +1,5 @@
1
+ const { CSVLoader } = require('./csvloader')
2
+
3
+ module.exports = {
4
+ CSVLoader
5
+ }
@@ -0,0 +1,55 @@
1
+ # Miscellanous pipeline items
2
+
3
+ ## PostgresSQLTask
4
+
5
+ Executes an arbitrary sql task either as part of another transformation or as an event
6
+
7
+ ### Example
8
+
9
+ ```js
10
+ const {
11
+ Connections,
12
+ Destinations,
13
+ Etl,
14
+ Loaders,
15
+ Misc,
16
+ Validators,
17
+ Transformers,
18
+ } = require("ffc-pay-etl-framework");
19
+
20
+ const etl = new Etl.Etl();
21
+
22
+ etl
23
+ .connections(
24
+ new Connections.PostgresSQLConnection({
25
+ connectionname: "MyConnection",
26
+ database: "MyDB",
27
+ username: "MyUserName",
28
+ password: "MyPassword",
29
+ host: "localhost",
30
+ port: 5432,
31
+ })
32
+ )
33
+ .beforeETL(
34
+ new Misc.PostgresSQLTask({
35
+ connection: "MyConnection",
36
+ sql: "INSERT INTO MyTable (column1, column2) VALUES ('apples','oranges');",
37
+ })
38
+ )
39
+ .loader(new Loaders.CSVLoader({ path: csvFile, columns: columns }))
40
+ .destination(
41
+ new Destinations.PostgresDestination({
42
+ connection: "MyConnection",
43
+ table: "target",
44
+ mapping: [
45
+ {
46
+ column: "Dist Code",
47
+ targetColumn: "dist_code",
48
+ targetType: "string",
49
+ },
50
+ ],
51
+ includeErrors: false,
52
+ })
53
+ )
54
+ .pump();
55
+ ```
File without changes
@@ -0,0 +1,71 @@
1
+ // @ts-nocheck
2
+ const { PassThrough } = require('stream')
3
+ const startPosOffset = 3
4
+ const endPosOffset = 1
5
+
6
+ function getPlaceHolders (sql) {
7
+ const columnNamePlaceholderRegex = /(?<=\$\{)(.*?)(?=})/g
8
+ const matches = Array.from(sql.matchAll(columnNamePlaceholderRegex))
9
+ return matches.map(match => ({
10
+ match: match[0],
11
+ value: match[0].split('.')[1],
12
+ collection: match[0].split('.')[0],
13
+ startPos: match.index - startPosOffset,
14
+ endPos: match.index + match[0].length - endPosOffset
15
+ }))
16
+ }
17
+
18
+ function doPlaceHolderValueInterpolations (chunk, sql, placeholders) {
19
+ placeholders.forEach(placeholder => {
20
+ sql = sql.replace(new RegExp(`\\$\{${placeholder.match}}`),
21
+ chunk[chunk[`_${placeholder.collection}`].indexOf(placeholder.value)])
22
+ })
23
+ return sql
24
+ }
25
+
26
+ function PostgresSQLTask (options) {
27
+ const passthrough = new PassThrough({
28
+ readableObjectMode: true,
29
+ writableObjectMode: true,
30
+ decodeStrings: false,
31
+ construct (callback) {
32
+ this.sql = options.sql
33
+ this.connectionname = options.connectionname
34
+ callback()
35
+ },
36
+ transform (chunk, _, callback) {
37
+ const placeholders = getPlaceHolders(this.sql)
38
+ if (placeholders.length === 0) {
39
+ this.connection.db.query(this.sql)
40
+ } else {
41
+ const interpolatedSql = doPlaceHolderValueInterpolations(chunk, this.sql, placeholders)
42
+ // TODO add more interpolation mechanisms to specify return values
43
+ // e.g. 'myReturnVal = SELECT MAX ID FROM TABLE;'
44
+ // and write to etl.store.myReturnVal or
45
+ // e.g. 'chunk.myReturnVal = SELECT MAX ID FROM TABLE;'
46
+ // and write to the chunk in say chunk.store.myReturnVal
47
+ this.connection.db.query(interpolatedSql)
48
+ }
49
+ callback(null, chunk)
50
+ }
51
+ })
52
+ // Should definately split this out into a mixin
53
+ Object.assign(passthrough, {
54
+ setConnection: function (connection) {
55
+ this.connection = connection
56
+ }.bind(passthrough),
57
+ getConnectionName: function () {
58
+ return this.connection.name
59
+ }.bind(passthrough),
60
+ setETL: function (etl) {
61
+ this.etl = etl
62
+ }.bind(passthrough)
63
+ })
64
+ return passthrough
65
+ }
66
+
67
+ module.exports = {
68
+ PostgresSQLTask,
69
+ getPlaceHolders,
70
+ doPlaceHolderValueInterpolations
71
+ }
@@ -0,0 +1,102 @@
1
+ # Transformers
2
+
3
+ Transformers manipulate row data and either replace the existing values or add new columns to each row depending on the type of transformer and the configuration.
4
+
5
+ Currently supported transformers are :
6
+
7
+ - ToUpperCaseTransformer
8
+ - FakerTransformer
9
+
10
+ ## FakerTransformer
11
+
12
+ ### Options
13
+
14
+ | option | description |
15
+ | ------- | ------------------------------------------------------------ |
16
+ | columns | Array of column transformations |
17
+ | locale | See https://fakerjs.dev/guide/localization for valid locales |
18
+
19
+ ### Usage
20
+
21
+ ```js
22
+ FakerTransformer({
23
+ columns: [
24
+ {
25
+ name: "column2",
26
+ faker: "company.name",
27
+ },
28
+ ],
29
+ });
30
+ ```
31
+
32
+ faker is a string value that can be any of the currently supported [apis](https://fakerjs.dev/api/)
33
+
34
+ e.g.
35
+
36
+ - finance.accountName
37
+ - person.firstName
38
+ - randomizer.next
39
+
40
+ To create UK post code
41
+
42
+ ```js
43
+ FakerTransformer({
44
+ columns: [
45
+ {
46
+ name: "column2",
47
+ faker: "location.zipCode",
48
+ },
49
+ ],
50
+ locale: "en_GB",
51
+ });
52
+ ```
53
+
54
+ ### Example
55
+
56
+ examples/csv-faker.js
57
+
58
+ ## ToUpperCaseTransformer
59
+
60
+ ### Options
61
+
62
+ | option | description |
63
+ | ------ | ------------------------------- |
64
+ | column | source column to be transformed |
65
+
66
+ ### Usage
67
+
68
+ ```js
69
+ ToUpperCaseTransformer({
70
+ column: "column2",
71
+ });
72
+ ```
73
+
74
+ ### Example
75
+
76
+ examples/csv-to-upper-case.js
77
+
78
+ ## StringReplaceTransformer
79
+
80
+ ### Options
81
+
82
+ | option | description |
83
+ | ------- | --------------------------------------- |
84
+ | column | source column to be transformed |
85
+ | find | search string to be replaced |
86
+ | replace | value to replace the search string with |
87
+
88
+ ### Usage
89
+
90
+ ```js
91
+ StringReplaceTransformer([
92
+ {
93
+ column: "column2",
94
+ find: "'",
95
+ replace: "''",
96
+ },
97
+ ]);
98
+ ```
99
+
100
+ ### Example
101
+
102
+ examples/csv-find-replace.js
@@ -0,0 +1,48 @@
1
+ const { Transform } = require('node:stream')
2
+
3
+ /**
4
+ *
5
+ * @param {Object} options
6
+ * @param {Array} options.columns {
7
+ * columns: [
8
+ * {
9
+ * name: "column-name",
10
+ * faker: "company.name"
11
+ * }
12
+ * ]
13
+ * }
14
+ * @param {String} options.locale
15
+ * @returns StreamReader
16
+ */
17
+ function FakerTransformer (options) {
18
+ const self = this
19
+ self.columns = options.columns
20
+ let faker
21
+ if (options.locale) {
22
+ faker = require(`@faker-js/faker/locale/${options.locale}`).faker
23
+ } else {
24
+ faker = require('@faker-js/faker').faker
25
+ }
26
+
27
+ function getFaker (fakerType) {
28
+ return fakerType.split('.').reduce((a, b) => {
29
+ return Object.keys(a).length === 0 ? faker[b] : a[b]
30
+ }, {})
31
+ }
32
+ return new Transform({
33
+ readableObjectMode: true,
34
+ writableObjectMode: true,
35
+ transform (chunk, _, callback) {
36
+ const { _columns } = chunk
37
+ self.columns.forEach(column => {
38
+ const colIndex = _columns.indexOf(column.name)
39
+ chunk[colIndex] = getFaker(column.faker)()
40
+ })
41
+ callback(null, chunk)
42
+ }
43
+ })
44
+ }
45
+
46
+ module.exports = {
47
+ FakerTransformer
48
+ }
@@ -0,0 +1,9 @@
1
+ const { ToUpperCaseTransformer } = require('./toUpperCaseTransformer')
2
+ const { FakerTransformer } = require('./fakerTransformer')
3
+ const { StringReplaceTransformer } = require('./stringReplaceTransformer')
4
+
5
+ module.exports = {
6
+ StringReplaceTransformer,
7
+ ToUpperCaseTransformer,
8
+ FakerTransformer
9
+ }
@@ -0,0 +1,35 @@
1
+ const { Transform } = require('node:stream')
2
+
3
+ /**
4
+ *
5
+ * @param {Object} options
6
+ * @param {String} options.column
7
+ * @returns Writable
8
+ */
9
+ function StringReplaceTransformer (options) {
10
+ const self = this
11
+ self.replacements = options
12
+
13
+ return new Transform({
14
+ readableObjectMode: true,
15
+ writableObjectMode: true,
16
+ transform (chunk, _, callback) {
17
+ const { _columns } = chunk
18
+ // @ts-ignore
19
+ self.replacements.forEach(r => {
20
+ const colIndex = _columns.indexOf(r.column)
21
+ if (r.all) {
22
+ chunk[colIndex] = chunk[colIndex].replaceAll(r.find, r.replace)
23
+ } else {
24
+ chunk[colIndex] = chunk[colIndex].replace(r.find, r.replace)
25
+ }
26
+ })
27
+
28
+ callback(null, chunk)
29
+ }
30
+ })
31
+ }
32
+
33
+ module.exports = {
34
+ StringReplaceTransformer
35
+ }
@@ -0,0 +1,27 @@
1
+ const { Transform } = require('node:stream')
2
+
3
+ /**
4
+ *
5
+ * @param {Object} options
6
+ * @param {String} options.column
7
+ * @returns Writable
8
+ */
9
+ function ToUpperCaseTransformer (options) {
10
+ const self = this
11
+ self.column = options.column
12
+
13
+ return new Transform({
14
+ readableObjectMode: true,
15
+ writableObjectMode: true,
16
+ transform (chunk, _, callback) {
17
+ const { _columns } = chunk
18
+ const colIndex = _columns.indexOf(self.column)
19
+ chunk[colIndex] = chunk[colIndex].toUpperCase()
20
+ callback(null, chunk)
21
+ }
22
+ })
23
+ }
24
+
25
+ module.exports = {
26
+ ToUpperCaseTransformer
27
+ }
@@ -0,0 +1,81 @@
1
+ # Validators
2
+
3
+ Validators are expected to check specific columns within each row against specific validation rules
4
+
5
+ Currently supported destinations are :
6
+
7
+ - RequiredValidator
8
+ - UniqueValidator
9
+ - MultiToolValidator
10
+
11
+ ## RequiredValidator
12
+
13
+ ### Options
14
+
15
+ | option | description |
16
+ | -------------- | ------------------------------------------------------------- |
17
+ | columns | Array of column indexes |
18
+
19
+ ### Usage
20
+
21
+ ```js
22
+ Validators.RequiredValidator({
23
+ columns: [0]
24
+ })
25
+ ```
26
+
27
+ ### Example
28
+
29
+ examples/csv-required-validator.js
30
+
31
+ ## UniqueValidator
32
+
33
+ ### Options
34
+
35
+ | option | description |
36
+ | -------------- | ------------------------------------------------------------- |
37
+ | column | Column to be tested for uniqueness |
38
+
39
+ ### Usage
40
+
41
+ ```js
42
+ Validators.UniqueValidator(
43
+ { column: "field_one" }
44
+ )
45
+ ```
46
+
47
+ ### Example
48
+
49
+ examples/csv-unique-validator.js
50
+
51
+ ## MultiToolValidator
52
+
53
+ ### Options
54
+
55
+ | option | description |
56
+ | -------------- | ------------------------------------------------------------- |
57
+ | column | Column to be tested for validity and validator specification |
58
+
59
+ NB Any validators specified [here](https://github.com/validatorjs/validator.js) can be used
60
+
61
+ ### Usage
62
+
63
+ ```js
64
+ Validators.MultiToolValidator( { columns: [
65
+ {
66
+ name: "SOIL TYPE PERCENT (Percent)",
67
+ validator: "isAlphanumeric",
68
+ args: {
69
+ locale: "en-GB",
70
+ options: {
71
+ ignore: "-;%/ "
72
+ }
73
+ }
74
+ }
75
+ ]
76
+ })
77
+ ```
78
+
79
+ ### Example
80
+
81
+ examples/csv-multi-tool-validator.js
@@ -0,0 +1,9 @@
1
+ const { RequiredValidator } = require('./requiredValidator')
2
+ const { UniqueValidator } = require('./uniqueValidator')
3
+ const { MultiToolValidator } = require('./multiToolValidator')
4
+
5
+ module.exports = {
6
+ RequiredValidator,
7
+ UniqueValidator,
8
+ MultiToolValidator
9
+ }
@@ -0,0 +1,49 @@
1
+ // @ts-nocheck
2
+ const { Transform } = require('stream')
3
+ const validator = require('validator')
4
+
5
+ /**
6
+ *
7
+ * @param {Object} options
8
+ * @param {Array} options.columns { columns: [
9
+ * {
10
+ * name: "column-name",
11
+ * validator: "isAlphanumeric",
12
+ * args: {
13
+ * locale: "en-GB",
14
+ * options: {
15
+ * ignore: "-;%/ "
16
+ * }
17
+ * }
18
+ * }
19
+ * ]
20
+ * }
21
+ * @returns Writable
22
+ */
23
+ function MultiToolValidator (options) {
24
+ function getValidator (validatorType) {
25
+ return validator[validatorType]
26
+ }
27
+ return new Transform({
28
+ readableObjectMode: true,
29
+ writableObjectMode: true,
30
+ decodeStrings: false,
31
+ construct (callback) {
32
+ this.columns = options.columns
33
+ callback()
34
+ },
35
+ transform (chunk, _, callback) {
36
+ const { _columns } = chunk
37
+ this.columns.forEach(column => {
38
+ const colIndex = _columns.indexOf(column.name)
39
+ const vtr = getValidator(column.validator)
40
+ if (!vtr(chunk[colIndex], ...Object.values(column.args))) { chunk.errors.push(`${column.validator} returned invalid status in column ${colIndex}`) }
41
+ })
42
+ callback(null, chunk)
43
+ }
44
+ })
45
+ }
46
+
47
+ module.exports = {
48
+ MultiToolValidator
49
+ }
@@ -0,0 +1,35 @@
1
+ // @ts-nocheck
2
+ const { Transform } = require('stream')
3
+
4
+ /**
5
+ *
6
+ * @param {Object} options
7
+ * @param {String} options.message
8
+ * @param {Array} options.columns
9
+ * @returns Transform
10
+ */
11
+ function RequiredValidator (options) {
12
+ return new Transform({
13
+ readableObjectMode: true,
14
+ writableObjectMode: true,
15
+ decodeStrings: false,
16
+ construct (callback) {
17
+ this.message = options.message ? options.message : 'Required value is null'
18
+ this.requiredColumns = options.columns
19
+ callback()
20
+ },
21
+ transform (chunk, _, callback) {
22
+ this.requiredColumns.forEach(column => {
23
+ const requiredVal = chunk[column]
24
+ if (requiredVal === undefined || requiredVal === '') {
25
+ chunk.errors.push(`${this.message} in column ${column}`)
26
+ }
27
+ })
28
+ callback(null, chunk)
29
+ }
30
+ })
31
+ }
32
+
33
+ module.exports = {
34
+ RequiredValidator
35
+ }
@@ -0,0 +1,41 @@
1
+ const { Transform } = require('node:stream')
2
+
3
+ /**
4
+ *
5
+ * @param {Object} options
6
+ * @param {String} options.message
7
+ * @param {String} options.column
8
+ * @returns Transform
9
+ */
10
+ function UniqueValidator (options) {
11
+ const self = this
12
+ self.column = options.column
13
+ self.message = options.message ? options.message : 'Expected unique value is not unique'
14
+ self.uniqueColumnRows = []
15
+ function checkIsUnique (data) {
16
+ return self.uniqueColumnRows.indexOf(data) === -1
17
+ }
18
+ return new Transform({
19
+ readableObjectMode: true,
20
+ writableObjectMode: true,
21
+ decodeStrings: false,
22
+ construct (callback) {
23
+ callback()
24
+ },
25
+ transform (chunk, _, callback) {
26
+ const { _columns } = chunk
27
+ const colIndex = _columns.indexOf(self.column)
28
+ const uniqueValue = chunk[colIndex]
29
+ if (checkIsUnique(uniqueValue)) {
30
+ self.uniqueColumnRows.push(uniqueValue)
31
+ } else {
32
+ chunk.errors.push(`${self.message} ${self.column}`)
33
+ }
34
+ callback(null, chunk)
35
+ }
36
+ })
37
+ }
38
+
39
+ module.exports = {
40
+ UniqueValidator
41
+ }