ffc-pay-etl-framework 1.1.14-alpha.9 → 1.2.0

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 (36) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/database_connections/index.js +3 -1
  3. package/dist/cjs/database_connections/providedConnection.js +29 -0
  4. package/dist/cjs/destinations/consoleDestination.js +4 -2
  5. package/dist/cjs/destinations/csvFileDestination.js +4 -2
  6. package/dist/cjs/destinations/postgresDestination.js +45 -24
  7. package/dist/cjs/destinations/sqlFileDestination.js +2 -1
  8. package/dist/cjs/lib/index.js +16 -5
  9. package/dist/cjs/loaders/csvloader.js +26 -7
  10. package/dist/cjs/types/database_connections/index.d.ts +2 -1
  11. package/dist/cjs/types/database_connections/providedConnection.d.ts +15 -0
  12. package/dist/cjs/types/database_connections/providedConnection.d.ts.map +1 -0
  13. package/dist/cjs/types/destinations/postgresDestination.d.ts +7 -1
  14. package/dist/cjs/types/destinations/postgresDestination.d.ts.map +1 -1
  15. package/dist/cjs/types/lib/index.d.ts +2 -1
  16. package/dist/cjs/types/lib/index.d.ts.map +1 -1
  17. package/dist/cjs/types/loaders/csvloader.d.ts +9 -3
  18. package/dist/cjs/types/loaders/csvloader.d.ts.map +1 -1
  19. package/dist/esm/database_connections/index.js +3 -1
  20. package/dist/esm/database_connections/providedConnection.js +20 -0
  21. package/dist/esm/destinations/consoleDestination.js +2 -2
  22. package/dist/esm/destinations/csvFileDestination.js +2 -2
  23. package/dist/esm/destinations/postgresDestination.js +42 -24
  24. package/dist/esm/destinations/sqlFileDestination.js +1 -1
  25. package/dist/esm/lib/index.js +16 -5
  26. package/dist/esm/loaders/csvloader.js +25 -7
  27. package/dist/esm/types/database_connections/index.d.ts +2 -1
  28. package/dist/esm/types/database_connections/providedConnection.d.ts +15 -0
  29. package/dist/esm/types/database_connections/providedConnection.d.ts.map +1 -0
  30. package/dist/esm/types/destinations/postgresDestination.d.ts +7 -1
  31. package/dist/esm/types/destinations/postgresDestination.d.ts.map +1 -1
  32. package/dist/esm/types/lib/index.d.ts +2 -1
  33. package/dist/esm/types/lib/index.d.ts.map +1 -1
  34. package/dist/esm/types/loaders/csvloader.d.ts +9 -3
  35. package/dist/esm/types/loaders/csvloader.d.ts.map +1 -1
  36. package/package.json +8 -3
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
4
4
 
5
- [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)
5
+ [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-)
6
6
 
7
7
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
8
8
 
@@ -98,8 +98,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
98
98
  <tbody>
99
99
  <tr>
100
100
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/suityou01"><img src="https://avatars.githubusercontent.com/u/7422644?v=4?s=100" width="100px;" alt="Charlie Benger-Stevenson"/><br /><sub><b>Charlie Benger-Stevenson</b></sub></a><br /><a href="https://github.com/DEFRA/ffc-pay-etl-framework/commits?author=suityou01" title="Code">💻</a></td>
101
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/GodsonLeigh"><img src="https://avatars.githubusercontent.com/u/139965284?v=4?s=100" width="100px;" alt="GodsonLeigh"/><br /><sub><b>GodsonLeigh</b></sub></a><br /><a href="https://github.com/DEFRA/ffc-pay-etl-framework/commits?author=GodsonLeigh" title="Code">💻</a></td>
102
- </tr>
101
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/GodsonLeigh"><img src="https://avatars.githubusercontent.com/u/139965284?v=4?s=100" width="100px;" alt="Leigh Godson"/><br /><sub><b>Leigh Godson</b></sub></a><br /><a href="https://github.com/DEFRA/ffc-pay-etl-framework/commits?author=GodsonLeigh" title="Code">💻</a></td>
102
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/samplackett"><img src="https://avatars.githubusercontent.com/u/60177449?v=4?s=100" width="100px;" alt="Sam Plackett"/><br /><sub><b>Sam Plackett</b></sub></a><br /><a href="https://github.com/DEFRA/ffc-pay-etl-framework/commits?author=samplackett" title="Code">💻</a></td> </tr>
103
103
  </tbody>
104
104
  <tfoot>
105
105
  <tr>
@@ -1,4 +1,6 @@
1
1
  const { PostgresDatabaseConnection } = require("./postgresDatabaseConnection");
2
+ const { ProvidedConnection } = require("./providedConnection");
2
3
  module.exports = {
3
- PostgresDatabaseConnection
4
+ PostgresDatabaseConnection,
5
+ ProvidedConnection
4
6
  };
@@ -0,0 +1,29 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /**
11
+ *
12
+ * @param {Object} options
13
+ * @param {Object} options.connectionname
14
+ * @param {Object} options.sequelize
15
+ * @returns Connection
16
+ */
17
+ function ProvidedConnection(options) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ const connectionname = options.connectionname;
20
+ const sequelize = options.sequelize;
21
+ return {
22
+ name: connectionname,
23
+ db: sequelize
24
+ };
25
+ });
26
+ }
27
+ module.exports = {
28
+ ProvidedConnection
29
+ };
@@ -10,10 +10,11 @@ function ConsoleDestination(options) {
10
10
  const writable = new Writable({
11
11
  objectMode: true,
12
12
  write(chunk, _, callback) {
13
+ var _a;
13
14
  if (chunk.errors.length === 0 || includeErrors)
14
15
  console.log(chunk);
15
16
  // @ts-ignore
16
- this.tasks && this.tasks.forEach(task => task.write(chunk));
17
+ (_a = this.tasks) === null || _a === void 0 ? void 0 : _a.forEach(task => task.write(chunk));
17
18
  callback();
18
19
  }
19
20
  });
@@ -22,7 +23,8 @@ function ConsoleDestination(options) {
22
23
  this.connection = connection;
23
24
  }.bind(writable),
24
25
  getConnectionName: function () {
25
- return this.connection && this.connection.name;
26
+ var _a;
27
+ return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.name;
26
28
  }.bind(writable),
27
29
  setTasks: function (tasks) {
28
30
  this.tasks = tasks;
@@ -26,6 +26,7 @@ function CSVFileDestination(options) {
26
26
  const writable = new Writable({
27
27
  objectMode: true,
28
28
  write(chunk, _, callback) {
29
+ var _a;
29
30
  if (!headersWritten && headers) {
30
31
  if (quotationMarks) {
31
32
  fs.writeFileSync(fileHandle, `${chunk._columns.map(c => `"${c}"`).join(",")}\n`);
@@ -44,7 +45,7 @@ function CSVFileDestination(options) {
44
45
  }
45
46
  }
46
47
  // @ts-ignore
47
- this.tasks && this.tasks.forEach(task => task.write(chunk));
48
+ (_a = this.tasks) === null || _a === void 0 ? void 0 : _a.forEach(task => task.write(chunk));
48
49
  lastChunk = chunk;
49
50
  callback();
50
51
  },
@@ -58,7 +59,8 @@ function CSVFileDestination(options) {
58
59
  this.connection = connection;
59
60
  }.bind(writable),
60
61
  getConnectionName: function () {
61
- return this.connection && this.connection.name;
62
+ var _a;
63
+ return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.name;
62
64
  }.bind(writable),
63
65
  setTasks: function (tasks) {
64
66
  this.tasks = tasks;
@@ -1,43 +1,54 @@
1
1
  const EventEmitter = require('node:events');
2
2
  const { Transform } = require('node:stream');
3
- const { Sequelize } = require('sequelize');
4
3
  const debug = require('debug')('destination');
5
4
  const DEFAULT_PORT = 5432;
6
5
  function isKeyWord(column) {
7
6
  return ['USER'].includes(column.toUpperCase());
8
7
  }
9
8
  function getMappingForColumn(mapping, column) {
9
+ if (mapping.length === 0) {
10
+ return {};
11
+ }
10
12
  const [map] = mapping.filter(m => m.column === column);
11
13
  return map;
12
14
  }
13
- function writeInsertStatement(columnMapping, table, chunk) {
14
- let statement = `INSERT INTO ${table} (${chunk._columns.map(column => {
15
+ function hasReturningColumns(mapping) {
16
+ return mapping.filter(m => m.returning === true).length > 0;
17
+ }
18
+ function getReturningColumns(mapping) {
19
+ return mapping.filter(m => m.returning === true).map(m => m === null || m === void 0 ? void 0 : m.targetColumn);
20
+ }
21
+ function writeInsertStatement(columnMapping, table, chunk, schema, ignoredColumns = []) {
22
+ const filteredColumns = chunk._columns.filter(column => {
15
23
  const mapping = getMappingForColumn(columnMapping, column);
16
- return mapping.targetColumn
17
- ?
18
- isKeyWord(mapping.targetColumn)
19
- ? `"${mapping.targetColumn}"`
20
- : mapping.targetColumn
21
- : isKeyWord(column)
22
- ? `"${mapping.column}"`
23
- : mapping.column;
24
+ const targetColumn = (mapping === null || mapping === void 0 ? void 0 : mapping.targetColumn) ? mapping === null || mapping === void 0 ? void 0 : mapping.targetColumn : mapping === null || mapping === void 0 ? void 0 : mapping.column;
25
+ return !ignoredColumns.includes(targetColumn);
26
+ });
27
+ let statement = `INSERT INTO ${schema !== null && schema !== void 0 ? schema : 'public'}."${table}" (${filteredColumns.map(column => {
28
+ const mapping = getMappingForColumn(columnMapping, column);
29
+ return (mapping === null || mapping === void 0 ? void 0 : mapping.targetColumn) ? `"${mapping.targetColumn}"` : `"${mapping === null || mapping === void 0 ? void 0 : mapping.column}"`;
24
30
  })
25
- .join(",")}) VALUES (${chunk._columns.map((column, index) => {
31
+ .join(",")}) VALUES (${filteredColumns.map(column => {
32
+ const index = chunk._columns.indexOf(column);
26
33
  const mapping = getMappingForColumn(columnMapping, column);
27
- if (!mapping)
28
- debug('Mapping not found for column %s', column);
29
- if (mapping.targetType === "number" && (isNaN(chunk[index]) || chunk[index] === '')) {
30
- debug('Source data is not a number');
34
+ if ((mapping === null || mapping === void 0 ? void 0 : mapping.targetType) === "number" && (isNaN(chunk[index]) || chunk[index] === '')) {
35
+ debug('Source data is not a number.');
31
36
  return 0;
32
37
  }
33
- if (mapping.targetType === "varchar" || mapping.targetType === "char") {
38
+ if ((mapping === null || mapping === void 0 ? void 0 : mapping.targetType) === "varchar" || (mapping === null || mapping === void 0 ? void 0 : mapping.targetType) === "char") {
34
39
  return `'${chunk[index]}'`;
35
40
  }
36
- if (mapping.targetType === "date") {
37
- return `to_date('${chunk[index]}','${mapping.format}')`;
41
+ if ((mapping === null || mapping === void 0 ? void 0 : mapping.targetType) === "date") {
42
+ if (!chunk[index]) {
43
+ return `''`;
44
+ }
45
+ return `to_timestamp('${chunk[index]}','${mapping === null || mapping === void 0 ? void 0 : mapping.format}')`;
38
46
  }
39
47
  return chunk[index] ? chunk[index] : 'null';
40
48
  })})`;
49
+ if (hasReturningColumns(columnMapping)) {
50
+ statement = statement + ` RETURNING ${getReturningColumns(columnMapping).join(',')}`;
51
+ }
41
52
  return statement;
42
53
  }
43
54
  /**
@@ -47,14 +58,19 @@ function writeInsertStatement(columnMapping, table, chunk) {
47
58
  * @param {Object} options.connectionname
48
59
  * @param {Object} options.mapping
49
60
  * @param {Object} options.includeErrors
61
+ * @param {String} [options.schema]
62
+ * @param {Array<String>} [options.ignoredColumns]
50
63
  * @returns Transform
51
64
  */
52
65
  function PostgresDestination(options) {
66
+ var _a;
53
67
  EventEmitter.call(this);
54
68
  const table = options.table;
55
69
  const connectionname = options.connectionname;
56
70
  const mapping = options.mapping;
57
71
  const includeErrors = options.includeErrors;
72
+ const schema = options.schema;
73
+ const ignoredColumns = (_a = options.ignoredColumns) !== null && _a !== void 0 ? _a : [];
58
74
  let lastChunk;
59
75
  const transform = new Transform({
60
76
  objectMode: true,
@@ -67,17 +83,18 @@ function PostgresDestination(options) {
67
83
  write(chunk, _, callback) {
68
84
  let insertStatement;
69
85
  // @ts-ignore
70
- if (chunk.errors.length === 0 | options.includeErrors) {
71
- insertStatement = writeInsertStatement(mapping, table, chunk);
86
+ if (chunk.errors.length === 0 || options.includeErrors) {
87
+ insertStatement = writeInsertStatement(mapping, table, chunk, schema, ignoredColumns);
72
88
  debug('Insert statement: [%s]', insertStatement);
73
89
  // @ts-ignore
74
90
  this.connection.db.query(insertStatement)
75
91
  .then(result => {
92
+ var _a;
76
93
  debug('result %o', result);
77
94
  chunk._result = result;
78
95
  lastChunk = chunk;
79
96
  // @ts-ignore
80
- this.tasks && this.tasks.forEach(task => task.write(chunk));
97
+ (_a = this.tasks) === null || _a === void 0 ? void 0 : _a.forEach(task => task.write(chunk));
81
98
  // @ts-ignore
82
99
  callback(null, chunk);
83
100
  }).catch(error => {
@@ -98,11 +115,13 @@ function PostgresDestination(options) {
98
115
  }
99
116
  });
100
117
  Object.assign(Transform.prototype, {
118
+ type: 'PostgresDestination',
101
119
  setConnection: function (connection) {
102
120
  this.connection = connection;
103
121
  }.bind(transform),
104
122
  getConnectionName: function () {
105
- return this.connection && this.connection.name;
123
+ var _a;
124
+ return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.name;
106
125
  }.bind(transform),
107
126
  setTasks: function (tasks) {
108
127
  this.tasks = tasks;
@@ -114,5 +133,7 @@ module.exports = {
114
133
  PostgresDestination,
115
134
  writeInsertStatement,
116
135
  isKeyWord,
117
- getMappingForColumn
136
+ getMappingForColumn,
137
+ hasReturningColumns,
138
+ getReturningColumns
118
139
  };
@@ -64,7 +64,8 @@ function SQLFileDestination(options) {
64
64
  this.connection = connection;
65
65
  }.bind(writable),
66
66
  getConnectionName: function () {
67
- return this.connection && this.connection.name;
67
+ var _a;
68
+ return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.name;
68
69
  }.bind(writable),
69
70
  setTasks: function (tasks) {
70
71
  this.tasks = tasks;
@@ -3,6 +3,7 @@ const EventEmitter = require('node:events');
3
3
  const util = require('node:util');
4
4
  const { RowMetaData } = require("./rowMetaData");
5
5
  const { compose } = require("node:stream");
6
+ const { Destinations } = require('..');
6
7
  /**
7
8
  * @typedef {Object} Etl
8
9
  * @function loader
@@ -18,7 +19,8 @@ const { compose } = require("node:stream");
18
19
  */
19
20
  function Etl() {
20
21
  EventEmitter.call(this);
21
- let self = this;
22
+ const self = this;
23
+ self.store = [];
22
24
  self.beforeETLList = [];
23
25
  self.connectionList = [];
24
26
  self.validatorList = [];
@@ -41,11 +43,12 @@ function Etl() {
41
43
  };
42
44
  this.beforeETL = (pipelineTask) => {
43
45
  const connectionname = pipelineTask.getConnectionName();
44
- const connection = this.connectionList.filter(c => c.name === connectionname);
46
+ const connection = this.connectionList.filter(c => c.name === connectionname)[0];
45
47
  if (!connection) {
46
48
  throw new Error(`Connection with name ${connectionname} not found`);
47
49
  }
48
50
  pipelineTask.setConnection(connection);
51
+ pipelineTask.setETL(self);
49
52
  self.beforeETLList.push(pipelineTask);
50
53
  return self;
51
54
  };
@@ -60,14 +63,22 @@ function Etl() {
60
63
  this.destination = (destination, ...tasks) => {
61
64
  const connectionname = destination.getConnectionName();
62
65
  const connection = this.connectionList.filter(c => c.name === connectionname)[0];
63
- if (connection) {
66
+ if (!connection && destination.type === 'PostgresDestination') {
67
+ throw new Error(`No connection could be found with name ${connectionname}`);
68
+ }
69
+ else {
64
70
  destination.setConnection(connection);
65
71
  }
66
- tasks && destination.setTasks(tasks);
72
+ if (tasks) {
73
+ for (const task of tasks) {
74
+ task.setETL(self);
75
+ }
76
+ destination.setTasks(tasks);
77
+ }
67
78
  self.destinationList.push(destination);
68
79
  return self;
69
80
  };
70
- this.transform = (transform, ...tasks) => {
81
+ this.transform = (transform) => {
71
82
  self.transformationList.push(transform);
72
83
  return self;
73
84
  };
@@ -2,21 +2,31 @@
2
2
  const fs = require("fs");
3
3
  const { Transform } = require("stream");
4
4
  const { parse } = require("csv-parse");
5
- const { stdout, stderr } = require("process");
6
5
  /**
7
6
  *
8
7
  * @param {Object} options
9
- * @param {String} options.path
8
+ * @param {String} [options.path]
9
+ * @param {ReadableStream} [options.stream]
10
+ * @param {Number} [options.startingLine]
11
+ * @param {Boolean} [options.relax]
10
12
  * @returns StreamReader
11
13
  */
12
14
  function CSVLoader(options) {
13
- let csvLoader = fs.createReadStream(options.path);
15
+ var _a, _b;
16
+ let csvLoader;
17
+ if (options.path) {
18
+ csvLoader = fs.createReadStream(options.path);
19
+ }
20
+ else {
21
+ csvLoader = options.stream;
22
+ }
14
23
  let lineCount = 1;
24
+ const fromLine = (_a = options.startingLine) !== null && _a !== void 0 ? _a : 2;
25
+ const relaxQuotes = (_b = options.relax) !== null && _b !== void 0 ? _b : false;
15
26
  csvLoader._columns = options.columns;
16
27
  csvLoader.pump = (csvLoader) => {
17
- return csvLoader
18
- .pipe(parse({ delimiter: ",", from_line: 2 }))
19
- .pipe(new Transform({
28
+ const parser = parse({ delimiter: ",", from_line: fromLine, relax_quotes: relaxQuotes });
29
+ const transformer = new Transform({
20
30
  readableObjectMode: true,
21
31
  writableObjectMode: true,
22
32
  emitClose: true,
@@ -24,9 +34,18 @@ function CSVLoader(options) {
24
34
  chunk["_columns"] = options.columns;
25
35
  chunk["_linecount"] = lineCount;
26
36
  lineCount += 1;
37
+ // remove non-printable characters
38
+ options.columns.forEach((_column, index) => {
39
+ if (chunk[index]) {
40
+ chunk[index] = chunk[index].replace(/[\x00-\x1F\x7F-\x9F]/g, '');
41
+ }
42
+ });
27
43
  callback(null, chunk);
28
44
  }
29
- }));
45
+ });
46
+ return csvLoader
47
+ .pipe(parser)
48
+ .pipe(transformer);
30
49
  };
31
50
  return csvLoader;
32
51
  }
@@ -1,3 +1,4 @@
1
- export { PostgresDatabaseConnection };
2
1
  import { PostgresDatabaseConnection } from "./postgresDatabaseConnection";
2
+ import { ProvidedConnection } from "./providedConnection";
3
+ export { PostgresDatabaseConnection, ProvidedConnection };
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ *
3
+ * @param {Object} options
4
+ * @param {Object} options.connectionname
5
+ * @param {Object} options.sequelize
6
+ * @returns Connection
7
+ */
8
+ export function ProvidedConnection(options: {
9
+ connectionname: any;
10
+ sequelize: any;
11
+ }): Promise<{
12
+ name: any;
13
+ db: any;
14
+ }>;
15
+ //# sourceMappingURL=providedConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providedConnection.d.ts","sourceRoot":"","sources":["../../../../src/database_connections/providedConnection.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH;IAJ2B,cAAc;IACd,SAAS;;;;GAUnC"}
@@ -5,6 +5,8 @@
5
5
  * @param {Object} options.connectionname
6
6
  * @param {Object} options.mapping
7
7
  * @param {Object} options.includeErrors
8
+ * @param {String} [options.schema]
9
+ * @param {Array<String>} [options.ignoredColumns]
8
10
  * @returns Transform
9
11
  */
10
12
  export function PostgresDestination(options: {
@@ -12,9 +14,13 @@ export function PostgresDestination(options: {
12
14
  connectionname: any;
13
15
  mapping: any;
14
16
  includeErrors: any;
17
+ schema?: string;
18
+ ignoredColumns?: Array<string>;
15
19
  }): Transform;
16
- export function writeInsertStatement(columnMapping: any, table: any, chunk: any): string;
20
+ export function writeInsertStatement(columnMapping: any, table: any, chunk: any, schema: any, ignoredColumns?: any[]): string;
17
21
  export function isKeyWord(column: any): boolean;
18
22
  export function getMappingForColumn(mapping: any, column: any): any;
23
+ export function hasReturningColumns(mapping: any): boolean;
24
+ export function getReturningColumns(mapping: any): any;
19
25
  import { Transform } from "stream";
20
26
  //# sourceMappingURL=postgresDestination.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgresDestination.d.ts","sourceRoot":"","sources":["../../../../src/destinations/postgresDestination.js"],"names":[],"mappings":"AA6CA;;;;;;;;GAQG;AACH;IAN2B,KAAK;IACL,cAAc;IACd,OAAO;IACP,aAAa;cA+DvC;AAnGD,yFA4BC;AArCD,gDAEC;AAED,oEAGC"}
1
+ {"version":3,"file":"postgresDestination.d.ts","sourceRoot":"","sources":["../../../../src/destinations/postgresDestination.js"],"names":[],"mappings":"AAyDA;;;;;;;;;;GAUG;AACH;IAR2B,KAAK;IACL,cAAc;IACd,OAAO;IACP,aAAa;IACZ,MAAM;IACC,cAAc,GAAtC,aAAa;cAkEvB;AA5GD,8HAgCC;AAlDD,gDAEC;AAED,oEAIC;AAED,2DAEC;AAED,uDAEC"}
@@ -14,6 +14,7 @@ export type Etl = any;
14
14
  */
15
15
  export function Etl(): void;
16
16
  export class Etl {
17
+ store: any[];
17
18
  beforeETLList: any[];
18
19
  connectionList: any[];
19
20
  validatorList: any[];
@@ -25,6 +26,6 @@ export class Etl {
25
26
  connection: (connection: any) => Etl;
26
27
  validator: (validator: any) => Etl;
27
28
  destination: (destination: any, ...tasks: any[]) => Etl;
28
- transform: (transform: any, ...tasks: any[]) => Etl;
29
+ transform: (transform: any) => Etl;
29
30
  }
30
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.js"],"names":[],"mappings":";AAMA;;;;;;;;GAQG;AAEH;;;GAGG;AACH,4BAwEC;;IArEG,qBAAuB;IACvB,sBAAwB;IACxB,qBAAuB;IACvB,0BAA4B;IAC5B,uBAAyB;IAEzB,6BAGC;IAED,gBAgBC;IAED,sCASC;IAED,qCAGC;IAED,mCAGC;IAED,wDAWC;IAED,oDAGC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.js"],"names":[],"mappings":";AAOA;;;;;;;;GAQG;AAEH;;;GAGG;AACH,4BAgFC;;IA7EG,aAAe;IACf,qBAAuB;IACvB,sBAAwB;IACxB,qBAAuB;IACvB,0BAA4B;IAC5B,uBAAyB;IAEzB,6BAGC;IAED,gBAgBC;IAED,sCAUC;IAED,qCAGC;IAED,mCAGC;IAED,wDAiBC;IAED,mCAGC"}
@@ -2,11 +2,17 @@
2
2
  /**
3
3
  *
4
4
  * @param {Object} options
5
- * @param {String} options.path
5
+ * @param {String} [options.path]
6
+ * @param {ReadableStream} [options.stream]
7
+ * @param {Number} [options.startingLine]
8
+ * @param {Boolean} [options.relax]
6
9
  * @returns StreamReader
7
10
  */
8
11
  export function CSVLoader(options: {
9
- path: string;
10
- }): fs.ReadStream;
12
+ path?: string;
13
+ stream?: ReadableStream;
14
+ startingLine?: number;
15
+ relax?: boolean;
16
+ }): ReadableStream<any> | fs.ReadStream;
11
17
  import fs = require("fs");
12
18
  //# sourceMappingURL=csvloader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"csvloader.d.ts","sourceRoot":"","sources":["../../../../src/loaders/csvloader.js"],"names":[],"mappings":";AAMA;;;;;GAKG;AACH;IAH2B,IAAI;kBAwB9B"}
1
+ {"version":3,"file":"csvloader.d.ts","sourceRoot":"","sources":["../../../../src/loaders/csvloader.js"],"names":[],"mappings":";AAKA;;;;;;;;GAQG;AACH;IAN4B,IAAI;IACI,MAAM,GAA/B,cAAc;IACG,YAAY;IACX,KAAK;wCAyCjC"}
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const { PostgresDatabaseConnection } = require("./postgresDatabaseConnection");
4
+ const { ProvidedConnection } = require("./providedConnection");
4
5
  module.exports = {
5
- PostgresDatabaseConnection
6
+ PostgresDatabaseConnection,
7
+ ProvidedConnection
6
8
  };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ *
5
+ * @param {Object} options
6
+ * @param {Object} options.connectionname
7
+ * @param {Object} options.sequelize
8
+ * @returns Connection
9
+ */
10
+ async function ProvidedConnection(options) {
11
+ const connectionname = options.connectionname;
12
+ const sequelize = options.sequelize;
13
+ return {
14
+ name: connectionname,
15
+ db: sequelize
16
+ };
17
+ }
18
+ module.exports = {
19
+ ProvidedConnection
20
+ };
@@ -15,7 +15,7 @@ function ConsoleDestination(options) {
15
15
  if (chunk.errors.length === 0 || includeErrors)
16
16
  console.log(chunk);
17
17
  // @ts-ignore
18
- this.tasks && this.tasks.forEach(task => task.write(chunk));
18
+ this.tasks?.forEach(task => task.write(chunk));
19
19
  callback();
20
20
  }
21
21
  });
@@ -24,7 +24,7 @@ function ConsoleDestination(options) {
24
24
  this.connection = connection;
25
25
  }.bind(writable),
26
26
  getConnectionName: function () {
27
- return this.connection && this.connection.name;
27
+ return this.connection?.name;
28
28
  }.bind(writable),
29
29
  setTasks: function (tasks) {
30
30
  this.tasks = tasks;
@@ -46,7 +46,7 @@ function CSVFileDestination(options) {
46
46
  }
47
47
  }
48
48
  // @ts-ignore
49
- this.tasks && this.tasks.forEach(task => task.write(chunk));
49
+ this.tasks?.forEach(task => task.write(chunk));
50
50
  lastChunk = chunk;
51
51
  callback();
52
52
  },
@@ -60,7 +60,7 @@ function CSVFileDestination(options) {
60
60
  this.connection = connection;
61
61
  }.bind(writable),
62
62
  getConnectionName: function () {
63
- return this.connection && this.connection.name;
63
+ return this.connection?.name;
64
64
  }.bind(writable),
65
65
  setTasks: function (tasks) {
66
66
  this.tasks = tasks;
@@ -2,44 +2,55 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const EventEmitter = require('node:events');
4
4
  const { Transform } = require('node:stream');
5
- const { Sequelize } = require('sequelize');
6
5
  const debug = require('debug')('destination');
7
6
  const DEFAULT_PORT = 5432;
8
7
  function isKeyWord(column) {
9
8
  return ['USER'].includes(column.toUpperCase());
10
9
  }
11
10
  function getMappingForColumn(mapping, column) {
11
+ if (mapping.length === 0) {
12
+ return {};
13
+ }
12
14
  const [map] = mapping.filter(m => m.column === column);
13
15
  return map;
14
16
  }
15
- function writeInsertStatement(columnMapping, table, chunk) {
16
- let statement = `INSERT INTO ${table} (${chunk._columns.map(column => {
17
+ function hasReturningColumns(mapping) {
18
+ return mapping.filter(m => m.returning === true).length > 0;
19
+ }
20
+ function getReturningColumns(mapping) {
21
+ return mapping.filter(m => m.returning === true).map(m => m?.targetColumn);
22
+ }
23
+ function writeInsertStatement(columnMapping, table, chunk, schema, ignoredColumns = []) {
24
+ const filteredColumns = chunk._columns.filter(column => {
17
25
  const mapping = getMappingForColumn(columnMapping, column);
18
- return mapping.targetColumn
19
- ?
20
- isKeyWord(mapping.targetColumn)
21
- ? `"${mapping.targetColumn}"`
22
- : mapping.targetColumn
23
- : isKeyWord(column)
24
- ? `"${mapping.column}"`
25
- : mapping.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}"`;
26
32
  })
27
- .join(",")}) VALUES (${chunk._columns.map((column, index) => {
33
+ .join(",")}) VALUES (${filteredColumns.map(column => {
34
+ const index = chunk._columns.indexOf(column);
28
35
  const mapping = getMappingForColumn(columnMapping, column);
29
- if (!mapping)
30
- debug('Mapping not found for column %s', column);
31
- if (mapping.targetType === "number" && (isNaN(chunk[index]) || chunk[index] === '')) {
32
- debug('Source data is not a number');
36
+ if (mapping?.targetType === "number" && (isNaN(chunk[index]) || chunk[index] === '')) {
37
+ debug('Source data is not a number.');
33
38
  return 0;
34
39
  }
35
- if (mapping.targetType === "varchar" || mapping.targetType === "char") {
40
+ if (mapping?.targetType === "varchar" || mapping?.targetType === "char") {
36
41
  return `'${chunk[index]}'`;
37
42
  }
38
- if (mapping.targetType === "date") {
39
- return `to_date('${chunk[index]}','${mapping.format}')`;
43
+ if (mapping?.targetType === "date") {
44
+ if (!chunk[index]) {
45
+ return `''`;
46
+ }
47
+ return `to_timestamp('${chunk[index]}','${mapping?.format}')`;
40
48
  }
41
49
  return chunk[index] ? chunk[index] : 'null';
42
50
  })})`;
51
+ if (hasReturningColumns(columnMapping)) {
52
+ statement = statement + ` RETURNING ${getReturningColumns(columnMapping).join(',')}`;
53
+ }
43
54
  return statement;
44
55
  }
45
56
  /**
@@ -49,6 +60,8 @@ function writeInsertStatement(columnMapping, table, chunk) {
49
60
  * @param {Object} options.connectionname
50
61
  * @param {Object} options.mapping
51
62
  * @param {Object} options.includeErrors
63
+ * @param {String} [options.schema]
64
+ * @param {Array<String>} [options.ignoredColumns]
52
65
  * @returns Transform
53
66
  */
54
67
  function PostgresDestination(options) {
@@ -57,6 +70,8 @@ function PostgresDestination(options) {
57
70
  const connectionname = options.connectionname;
58
71
  const mapping = options.mapping;
59
72
  const includeErrors = options.includeErrors;
73
+ const schema = options.schema;
74
+ const ignoredColumns = options.ignoredColumns ?? [];
60
75
  let lastChunk;
61
76
  const transform = new Transform({
62
77
  objectMode: true,
@@ -69,8 +84,8 @@ function PostgresDestination(options) {
69
84
  write(chunk, _, callback) {
70
85
  let insertStatement;
71
86
  // @ts-ignore
72
- if (chunk.errors.length === 0 | options.includeErrors) {
73
- insertStatement = writeInsertStatement(mapping, table, chunk);
87
+ if (chunk.errors.length === 0 || options.includeErrors) {
88
+ insertStatement = writeInsertStatement(mapping, table, chunk, schema, ignoredColumns);
74
89
  debug('Insert statement: [%s]', insertStatement);
75
90
  // @ts-ignore
76
91
  this.connection.db.query(insertStatement)
@@ -79,7 +94,7 @@ function PostgresDestination(options) {
79
94
  chunk._result = result;
80
95
  lastChunk = chunk;
81
96
  // @ts-ignore
82
- this.tasks && this.tasks.forEach(task => task.write(chunk));
97
+ this.tasks?.forEach(task => task.write(chunk));
83
98
  // @ts-ignore
84
99
  callback(null, chunk);
85
100
  }).catch(error => {
@@ -100,11 +115,12 @@ function PostgresDestination(options) {
100
115
  }
101
116
  });
102
117
  Object.assign(Transform.prototype, {
118
+ type: 'PostgresDestination',
103
119
  setConnection: function (connection) {
104
120
  this.connection = connection;
105
121
  }.bind(transform),
106
122
  getConnectionName: function () {
107
- return this.connection && this.connection.name;
123
+ return this.connection?.name;
108
124
  }.bind(transform),
109
125
  setTasks: function (tasks) {
110
126
  this.tasks = tasks;
@@ -116,5 +132,7 @@ module.exports = {
116
132
  PostgresDestination,
117
133
  writeInsertStatement,
118
134
  isKeyWord,
119
- getMappingForColumn
135
+ getMappingForColumn,
136
+ hasReturningColumns,
137
+ getReturningColumns
120
138
  };
@@ -66,7 +66,7 @@ function SQLFileDestination(options) {
66
66
  this.connection = connection;
67
67
  }.bind(writable),
68
68
  getConnectionName: function () {
69
- return this.connection && this.connection.name;
69
+ return this.connection?.name;
70
70
  }.bind(writable),
71
71
  setTasks: function (tasks) {
72
72
  this.tasks = tasks;
@@ -5,6 +5,7 @@ const EventEmitter = require('node:events');
5
5
  const util = require('node:util');
6
6
  const { RowMetaData } = require("./rowMetaData");
7
7
  const { compose } = require("node:stream");
8
+ const { Destinations } = require('..');
8
9
  /**
9
10
  * @typedef {Object} Etl
10
11
  * @function loader
@@ -20,7 +21,8 @@ const { compose } = require("node:stream");
20
21
  */
21
22
  function Etl() {
22
23
  EventEmitter.call(this);
23
- let self = this;
24
+ const self = this;
25
+ self.store = [];
24
26
  self.beforeETLList = [];
25
27
  self.connectionList = [];
26
28
  self.validatorList = [];
@@ -43,11 +45,12 @@ function Etl() {
43
45
  };
44
46
  this.beforeETL = (pipelineTask) => {
45
47
  const connectionname = pipelineTask.getConnectionName();
46
- const connection = this.connectionList.filter(c => c.name === connectionname);
48
+ const connection = this.connectionList.filter(c => c.name === connectionname)[0];
47
49
  if (!connection) {
48
50
  throw new Error(`Connection with name ${connectionname} not found`);
49
51
  }
50
52
  pipelineTask.setConnection(connection);
53
+ pipelineTask.setETL(self);
51
54
  self.beforeETLList.push(pipelineTask);
52
55
  return self;
53
56
  };
@@ -62,14 +65,22 @@ function Etl() {
62
65
  this.destination = (destination, ...tasks) => {
63
66
  const connectionname = destination.getConnectionName();
64
67
  const connection = this.connectionList.filter(c => c.name === connectionname)[0];
65
- if (connection) {
68
+ if (!connection && destination.type === 'PostgresDestination') {
69
+ throw new Error(`No connection could be found with name ${connectionname}`);
70
+ }
71
+ else {
66
72
  destination.setConnection(connection);
67
73
  }
68
- tasks && destination.setTasks(tasks);
74
+ if (tasks) {
75
+ for (const task of tasks) {
76
+ task.setETL(self);
77
+ }
78
+ destination.setTasks(tasks);
79
+ }
69
80
  self.destinationList.push(destination);
70
81
  return self;
71
82
  };
72
- this.transform = (transform, ...tasks) => {
83
+ this.transform = (transform) => {
73
84
  self.transformationList.push(transform);
74
85
  return self;
75
86
  };
@@ -4,21 +4,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const fs = require("fs");
5
5
  const { Transform } = require("stream");
6
6
  const { parse } = require("csv-parse");
7
- const { stdout, stderr } = require("process");
8
7
  /**
9
8
  *
10
9
  * @param {Object} options
11
- * @param {String} options.path
10
+ * @param {String} [options.path]
11
+ * @param {ReadableStream} [options.stream]
12
+ * @param {Number} [options.startingLine]
13
+ * @param {Boolean} [options.relax]
12
14
  * @returns StreamReader
13
15
  */
14
16
  function CSVLoader(options) {
15
- let csvLoader = fs.createReadStream(options.path);
17
+ let csvLoader;
18
+ if (options.path) {
19
+ csvLoader = fs.createReadStream(options.path);
20
+ }
21
+ else {
22
+ csvLoader = options.stream;
23
+ }
16
24
  let lineCount = 1;
25
+ const fromLine = options.startingLine ?? 2;
26
+ const relaxQuotes = options.relax ?? false;
17
27
  csvLoader._columns = options.columns;
18
28
  csvLoader.pump = (csvLoader) => {
19
- return csvLoader
20
- .pipe(parse({ delimiter: ",", from_line: 2 }))
21
- .pipe(new Transform({
29
+ const parser = parse({ delimiter: ",", from_line: fromLine, relax_quotes: relaxQuotes });
30
+ const transformer = new Transform({
22
31
  readableObjectMode: true,
23
32
  writableObjectMode: true,
24
33
  emitClose: true,
@@ -26,9 +35,18 @@ function CSVLoader(options) {
26
35
  chunk["_columns"] = options.columns;
27
36
  chunk["_linecount"] = lineCount;
28
37
  lineCount += 1;
38
+ // remove non-printable characters
39
+ options.columns.forEach((_column, index) => {
40
+ if (chunk[index]) {
41
+ chunk[index] = chunk[index].replace(/[\x00-\x1F\x7F-\x9F]/g, '');
42
+ }
43
+ });
29
44
  callback(null, chunk);
30
45
  }
31
- }));
46
+ });
47
+ return csvLoader
48
+ .pipe(parser)
49
+ .pipe(transformer);
32
50
  };
33
51
  return csvLoader;
34
52
  }
@@ -1,3 +1,4 @@
1
- export { PostgresDatabaseConnection };
2
1
  import { PostgresDatabaseConnection } from "./postgresDatabaseConnection";
2
+ import { ProvidedConnection } from "./providedConnection";
3
+ export { PostgresDatabaseConnection, ProvidedConnection };
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ *
3
+ * @param {Object} options
4
+ * @param {Object} options.connectionname
5
+ * @param {Object} options.sequelize
6
+ * @returns Connection
7
+ */
8
+ export function ProvidedConnection(options: {
9
+ connectionname: any;
10
+ sequelize: any;
11
+ }): Promise<{
12
+ name: any;
13
+ db: any;
14
+ }>;
15
+ //# sourceMappingURL=providedConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providedConnection.d.ts","sourceRoot":"","sources":["../../../../src/database_connections/providedConnection.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH;IAJ2B,cAAc;IACd,SAAS;;;;GAUnC"}
@@ -5,6 +5,8 @@
5
5
  * @param {Object} options.connectionname
6
6
  * @param {Object} options.mapping
7
7
  * @param {Object} options.includeErrors
8
+ * @param {String} [options.schema]
9
+ * @param {Array<String>} [options.ignoredColumns]
8
10
  * @returns Transform
9
11
  */
10
12
  export function PostgresDestination(options: {
@@ -12,9 +14,13 @@ export function PostgresDestination(options: {
12
14
  connectionname: any;
13
15
  mapping: any;
14
16
  includeErrors: any;
17
+ schema?: string;
18
+ ignoredColumns?: Array<string>;
15
19
  }): Transform;
16
- export function writeInsertStatement(columnMapping: any, table: any, chunk: any): string;
20
+ export function writeInsertStatement(columnMapping: any, table: any, chunk: any, schema: any, ignoredColumns?: any[]): string;
17
21
  export function isKeyWord(column: any): boolean;
18
22
  export function getMappingForColumn(mapping: any, column: any): any;
23
+ export function hasReturningColumns(mapping: any): boolean;
24
+ export function getReturningColumns(mapping: any): any;
19
25
  import { Transform } from "stream";
20
26
  //# sourceMappingURL=postgresDestination.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgresDestination.d.ts","sourceRoot":"","sources":["../../../../src/destinations/postgresDestination.js"],"names":[],"mappings":"AA6CA;;;;;;;;GAQG;AACH;IAN2B,KAAK;IACL,cAAc;IACd,OAAO;IACP,aAAa;cA+DvC;AAnGD,yFA4BC;AArCD,gDAEC;AAED,oEAGC"}
1
+ {"version":3,"file":"postgresDestination.d.ts","sourceRoot":"","sources":["../../../../src/destinations/postgresDestination.js"],"names":[],"mappings":"AAyDA;;;;;;;;;;GAUG;AACH;IAR2B,KAAK;IACL,cAAc;IACd,OAAO;IACP,aAAa;IACZ,MAAM;IACC,cAAc,GAAtC,aAAa;cAkEvB;AA5GD,8HAgCC;AAlDD,gDAEC;AAED,oEAIC;AAED,2DAEC;AAED,uDAEC"}
@@ -14,6 +14,7 @@ export type Etl = any;
14
14
  */
15
15
  export function Etl(): void;
16
16
  export class Etl {
17
+ store: any[];
17
18
  beforeETLList: any[];
18
19
  connectionList: any[];
19
20
  validatorList: any[];
@@ -25,6 +26,6 @@ export class Etl {
25
26
  connection: (connection: any) => Etl;
26
27
  validator: (validator: any) => Etl;
27
28
  destination: (destination: any, ...tasks: any[]) => Etl;
28
- transform: (transform: any, ...tasks: any[]) => Etl;
29
+ transform: (transform: any) => Etl;
29
30
  }
30
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.js"],"names":[],"mappings":";AAMA;;;;;;;;GAQG;AAEH;;;GAGG;AACH,4BAwEC;;IArEG,qBAAuB;IACvB,sBAAwB;IACxB,qBAAuB;IACvB,0BAA4B;IAC5B,uBAAyB;IAEzB,6BAGC;IAED,gBAgBC;IAED,sCASC;IAED,qCAGC;IAED,mCAGC;IAED,wDAWC;IAED,oDAGC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.js"],"names":[],"mappings":";AAOA;;;;;;;;GAQG;AAEH;;;GAGG;AACH,4BAgFC;;IA7EG,aAAe;IACf,qBAAuB;IACvB,sBAAwB;IACxB,qBAAuB;IACvB,0BAA4B;IAC5B,uBAAyB;IAEzB,6BAGC;IAED,gBAgBC;IAED,sCAUC;IAED,qCAGC;IAED,mCAGC;IAED,wDAiBC;IAED,mCAGC"}
@@ -2,11 +2,17 @@
2
2
  /**
3
3
  *
4
4
  * @param {Object} options
5
- * @param {String} options.path
5
+ * @param {String} [options.path]
6
+ * @param {ReadableStream} [options.stream]
7
+ * @param {Number} [options.startingLine]
8
+ * @param {Boolean} [options.relax]
6
9
  * @returns StreamReader
7
10
  */
8
11
  export function CSVLoader(options: {
9
- path: string;
10
- }): fs.ReadStream;
12
+ path?: string;
13
+ stream?: ReadableStream;
14
+ startingLine?: number;
15
+ relax?: boolean;
16
+ }): ReadableStream<any> | fs.ReadStream;
11
17
  import fs = require("fs");
12
18
  //# sourceMappingURL=csvloader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"csvloader.d.ts","sourceRoot":"","sources":["../../../../src/loaders/csvloader.js"],"names":[],"mappings":";AAMA;;;;;GAKG;AACH;IAH2B,IAAI;kBAwB9B"}
1
+ {"version":3,"file":"csvloader.d.ts","sourceRoot":"","sources":["../../../../src/loaders/csvloader.js"],"names":[],"mappings":";AAKA;;;;;;;;GAQG;AACH;IAN4B,IAAI;IACI,MAAM,GAA/B,cAAc;IACG,YAAY;IACX,KAAK;wCAyCjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ffc-pay-etl-framework",
3
- "version": "1.1.14-alpha.9",
3
+ "version": "1.2.0",
4
4
  "publisher": "Defra",
5
5
  "main": "dist/cjs/index.js",
6
6
  "private": false,
@@ -13,12 +13,17 @@
13
13
  "build:esm": "tsc -p ./tsconfig.esm.json && mv dist/esm/index.js dist/esm/index.mjs",
14
14
  "build:cjs": "tsc -p ./tsconfig.cjs.json",
15
15
  "prepack": "npm run build",
16
- "example": "node ./src/examples/csv-multi-tool-validator.js"
16
+ "example": "node ./src/examples/sql-return-values.js"
17
17
  },
18
18
  "keywords": [
19
19
  "ETL"
20
20
  ],
21
- "author": "Charlie Benger-Stevenson",
21
+ "author": "Defra",
22
+ "contributors": [
23
+ "Charlie Benger-Stevenson charlie.benger-stevenson.external@eviden.com",
24
+ "Leigh Godson leigh.godson@eviden.com",
25
+ "Sam Plackett samuel.plackett@eviden.com"
26
+ ],
22
27
  "license": "OGL-UK-3.0",
23
28
  "repository": "https://github.com/DEFRA/ffc-pay-etl-framework",
24
29
  "description": "A framework for creating ETL pipelines in node",