@engine9-io/input-tools 1.9.3 → 1.9.6

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/ForEachEntry.js CHANGED
@@ -24,7 +24,7 @@ class ForEachEntry {
24
24
  this.fileUtilities = new FileUtilities({ accountId });
25
25
  }
26
26
 
27
- getOutputStream({ name, postfix = '.timeline.csv', validatorFunction = () => true }) {
27
+ getOutputStream({ name, filename, postfix = '.timeline.csv', validatorFunction = () => true }) {
28
28
  this.outputStreams = this.outputStreams || {};
29
29
  if (this.outputStreams[name]?.items) return this.outputStreams[name].items;
30
30
 
@@ -33,12 +33,14 @@ class ForEachEntry {
33
33
  };
34
34
 
35
35
  return this.outputStreams[name].mutex.runExclusive(async () => {
36
+ let f = filename || (await getTempFilename({ postfix }));
37
+
36
38
  const fileInfo = {
37
- filename: await getTempFilename({ postfix }),
39
+ filename: f,
38
40
  records: 0
39
41
  };
40
42
 
41
- debug(`Output file requested, writing output to to: ${fileInfo.filename}`);
43
+ debug(`Output file requested ${name}, writing output to to: ${fileInfo.filename}`);
42
44
  const outputStream = new ValidatingReadable(
43
45
  {
44
46
  objectMode: true
@@ -206,6 +206,17 @@ Worker.prototype.xlsxToObjectStream = async function (options) {
206
206
  return { stream };
207
207
  };
208
208
 
209
+ Worker.prototype.getFormat = async function (options) {
210
+ const { sourcePostfix, filename, format: formatOverride } = options;
211
+ let postfix = sourcePostfix || filename.toLowerCase().split('.').pop();
212
+
213
+ if (postfix === 'gz') {
214
+ postfix = filename.toLowerCase().split('.');
215
+ postfix = postfix[postfix.length - 2];
216
+ }
217
+ return formatOverride || postfix;
218
+ };
219
+
209
220
  /*
210
221
  Commonly used method to transform a file into a stream of objects.
211
222
  */
@@ -635,6 +646,8 @@ Worker.prototype.write = async function (opts) {
635
646
  content
636
647
  });
637
648
  } else {
649
+ const directory = path.dirname(filename);
650
+ await fsp.mkdir(directory, { recursive: true });
638
651
  await fsp.writeFile(filename, content);
639
652
  }
640
653
  return { success: true, filename };
@@ -953,6 +966,34 @@ Worker.prototype.head.metadata = {
953
966
  }
954
967
  };
955
968
 
969
+ Worker.prototype.columns = async function (options) {
970
+ const head = await this.head(options);
971
+ if (head.length == 0) {
972
+ return {
973
+ records: 0,
974
+ likelyHeaderLines: 0,
975
+ columns: []
976
+ };
977
+ }
978
+
979
+ let likelyHeaderLines = 1;
980
+ const columns = Object.keys(head[0]);
981
+ let s = columns.join(',');
982
+ if (s.match(/[()@#%!]/)) {
983
+ likelyHeaderLines = 0;
984
+ }
985
+ return {
986
+ likelyHeaderLines,
987
+ columns
988
+ };
989
+ };
990
+
991
+ Worker.prototype.columns.metadata = {
992
+ options: {
993
+ filename: { required: true }
994
+ }
995
+ };
996
+
956
997
  Worker.prototype.count = async function (options) {
957
998
  const { stream } = await this.fileToObjectStream(options);
958
999
  const sample = [];
@@ -1084,17 +1125,14 @@ diff that allows for unordered files, and doesn't store full objects in memory.
1084
1125
  Requires 2 passes of the files,
1085
1126
  but that's a better tradeoff than trying to store huge files in memory
1086
1127
  */
1087
- Worker.prototype.diff = async function ({
1088
- fileA,
1089
- fileB,
1090
- uniqueFunction: ufOpt,
1091
- fields,
1092
- includeDuplicateSourceRecords
1093
- }) {
1094
- if (ufOpt && fields) throw new Error('fields and uniqueFunction cannot both be specified');
1128
+ Worker.prototype.diff = async function (options) {
1129
+ const { fileA, fileB, uniqueFunction: ufOpt, columns, includeDuplicateSourceRecords } = options;
1130
+ if (options.fields) throw new Error('fields is deprecated, use columns');
1131
+
1132
+ if (ufOpt && columns) throw new Error('fields and uniqueFunction cannot both be specified');
1095
1133
  let uniqueFunction = ufOpt;
1096
- if (!uniqueFunction && fields) {
1097
- const farr = getStringArray(fields);
1134
+ if (!uniqueFunction && columns) {
1135
+ const farr = getStringArray(columns);
1098
1136
  uniqueFunction = (o) => farr.map((f) => o[f] || '').join('.');
1099
1137
  }
1100
1138
 
@@ -1120,7 +1158,7 @@ Worker.prototype.diff.metadata = {
1120
1158
  options: {
1121
1159
  fileA: {},
1122
1160
  fileB: {},
1123
- fields: { description: 'Fields to use for uniqueness -- aka primary key. Defaults to JSON of line' },
1161
+ columns: { description: 'Columns to use for uniqueness -- aka primary key. Defaults to JSON of line' },
1124
1162
  uniqueFunction: {},
1125
1163
  includeDuplicateSourceRecords: {
1126
1164
  description: 'Sometimes you want the output to include source dupes, sometimes not, default false'
package/file/tools.js CHANGED
@@ -351,6 +351,15 @@ function appendPostfix(filename, postfix) {
351
351
  return filenameParts.slice(0, -1).concat(targetFile).join('/');
352
352
  }
353
353
 
354
+ function parseJSON5(o, defaultVal) {
355
+ if (o) {
356
+ if (typeof o === 'object') return o;
357
+ if (typeof o === 'string') return JSON5.parse(o);
358
+ throw new Error(`Could not parse object:${o}`);
359
+ }
360
+ return defaultVal || o;
361
+ }
362
+
354
363
  module.exports = {
355
364
  appendPostfix,
356
365
  bool,
@@ -364,6 +373,7 @@ module.exports = {
364
373
  getPacketFiles,
365
374
  getStringArray,
366
375
  makeStrings,
376
+ parseJSON5,
367
377
  relativeDate,
368
378
  streamPacket,
369
379
  writeTempFile
package/index.js CHANGED
@@ -15,18 +15,19 @@ const FileUtilities = require('./file/FileUtilities');
15
15
  const {
16
16
  appendPostfix,
17
17
  bool,
18
- getManifest,
18
+ getBatchTransform,
19
+ getDebatchTransform,
19
20
  getFile,
21
+ getManifest,
22
+ getPacketFiles,
23
+ getStringArray,
20
24
  downloadFile,
21
25
  getTempFilename,
22
26
  getTempDir,
23
27
  isValidDate,
28
+ parseJSON5,
24
29
  relativeDate,
25
30
  streamPacket,
26
- getPacketFiles,
27
- getBatchTransform,
28
- getDebatchTransform,
29
- getStringArray,
30
31
  makeStrings,
31
32
  writeTempFile
32
33
  } = require('./file/tools');
@@ -421,6 +422,7 @@ module.exports = {
421
422
  isValidDate,
422
423
  makeStrings,
423
424
  ObjectError,
425
+ parseJSON5,
424
426
  relativeDate,
425
427
  streamPacket,
426
428
  TIMELINE_ENTRY_TYPES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engine9-io/input-tools",
3
- "version": "1.9.3",
3
+ "version": "1.9.6",
4
4
  "description": "Tools for dealing with Engine9 inputs",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/file.js CHANGED
@@ -1,18 +1,24 @@
1
- const {
2
- it,
3
- } = require('node:test');
1
+ const { it } = require('node:test');
4
2
  const assert = require('node:assert');
5
3
  const debug = require('debug')('files');
6
4
 
7
5
  const { FileUtilities } = require('../index');
8
6
 
9
7
  it('Should list a directory', async () => {
10
- const futil=new FileUtilities({accountId:'test'});
11
- let files=await futil.list({directory:'.'});
12
- assert(files.length,"Should have some files");
8
+ const futil = new FileUtilities({ accountId: 'test' });
9
+ let files = await futil.list({ directory: '.' });
10
+ assert(files.length, 'Should have some files');
13
11
  debug(files);
14
- let startTest=await futil.list({directory:'.',start:'2040-01-01'});
15
- assert(startTest.length===0,"Should NOT have any files before future start date");
16
- let endTest=await futil.list({directory:'.',end:'1900-01-01'});
17
- assert(endTest.length===0,"Should NOT have any files before past end date");
12
+ let startTest = await futil.list({ directory: '.', start: '2040-01-01' });
13
+ assert(startTest.length === 0, 'Should NOT have any files before future start date');
14
+ let endTest = await futil.list({ directory: '.', end: '1900-01-01' });
15
+ assert(endTest.length === 0, 'Should NOT have any files before past end date');
16
+ });
17
+
18
+ it('Should be able to analyze CSV files with and without header lines', async () => {
19
+ const futil = new FileUtilities({ accountId: 'test' });
20
+ const f1 = await futil.columns({ filename: __dirname + '/sample/fileWithHead.csv' });
21
+ assert.equal(f1.likelyHeaderLines, 1, 'Number of header lines should be 1');
22
+ const f2 = await futil.columns({ filename: __dirname + '/sample/fileWithoutHead.csv' });
23
+ assert.equal(f2.likelyHeaderLines, 0, 'Number of header lines should be 1');
18
24
  });
@@ -0,0 +1,56 @@
1
+ const { describe, it } = require('node:test');
2
+ const assert = require('node:assert');
3
+ const debug = require('debug')('test/forEach');
4
+
5
+ const { ForEachEntry } = require('../../index');
6
+
7
+ describe('Test Person File For Each', async () => {
8
+ it('forEachPerson Should loop through 1000 sample people', async () => {
9
+ let counter = 0;
10
+ const forEach = new ForEachEntry();
11
+ const result = await forEach.process({
12
+ packet: 'test/sample/1000_message.packet.zip',
13
+ batchSize: 50,
14
+ bindings: {
15
+ timelineOutputFileStream: {
16
+ path: 'output.timeline',
17
+ options: {
18
+ entry_type: 'ENTRY_OPTION'
19
+ }
20
+ },
21
+ sampleOutputFileStream: {
22
+ path: 'output.stream'
23
+ }
24
+ },
25
+ async transform(props) {
26
+ const { batch, timelineOutputFileStream, sampleOutputFileStream } = props;
27
+
28
+ batch.forEach((p) => {
29
+ if (Math.random() > 0.9) {
30
+ sampleOutputFileStream.push({
31
+ // for testing we don't need real person_ids
32
+ person_id: p.person_id || Math.floor(Math.random() * 1000000),
33
+ email: p.email,
34
+ entry_type: 'SAMPLE_OUTPUT'
35
+ });
36
+ }
37
+ timelineOutputFileStream.push({
38
+ // for testing we don't need real person_ids
39
+ person_id: p.person_id || Math.floor(Math.random() * 1000000),
40
+ email: p.email,
41
+ entry_type: 'EMAIL_DELIVERED'
42
+ });
43
+ });
44
+
45
+ batch.forEach(() => {
46
+ counter += 1;
47
+ });
48
+ }
49
+ });
50
+ assert(result.outputFiles?.timelineOutputFileStream?.[0]?.records);
51
+ assert(result.outputFiles?.sampleOutputFileStream?.[0]?.records);
52
+ debug(result);
53
+ assert.equal(counter, 1000, `Expected to loop through 1000 people, actual:${counter}`);
54
+ });
55
+ debug('Completed tests');
56
+ });
@@ -0,0 +1,3 @@
1
+ first,last,email,phone
2
+ Bob,Smith,test@test.com,(703) 555-5555
3
+ Jane,Doe,test2@test.com,(703) 555-5432
@@ -0,0 +1,2 @@
1
+ Bob,Smith,test@test.com,(703) 555-5555
2
+ Jane,Doe,test2@test.com,(703) 555-5432
package/timelineTypes.js CHANGED
@@ -26,6 +26,16 @@ const TRANSACTION_REFUND = 15;
26
26
  const SEGMENT_PERSON_ADD = 16;
27
27
  const SEGMENT_PERSON_REMOVE = 17;
28
28
 
29
+ // unknown generic conversion on a message
30
+ const MESSAGE_CONVERSION = 20;
31
+ // advocacy conversion on a message
32
+ const MESSAGE_CONVERSION_ADVOCACY = 21;
33
+ // unknown transaction conversion on a message
34
+ const MESSAGE_CONVERSION_TRANSACTION = 22;
35
+
36
+ const MESSAGE_DELIVERY_FAILURE_SHOULD_RETRY = 25;
37
+ const MESSAGE_DELIVERY_FAILURE_SHOULD_NOT_RETRY = 26;
38
+
29
39
  const SMS_SEND = 30;
30
40
  const SMS_DELIVERED = 31;
31
41
  const SMS_CLICK = 33;
@@ -54,16 +64,6 @@ const FORM_SUBMIT = 60;
54
64
  const FORM_PETITION = 61;
55
65
  const FORM_PETITION_CONTACT_TARGET = 62;
56
66
 
57
- // unknown generic conversion on a message
58
- const MESSAGE_CONVERSION = 63;
59
- // advocacy conversion on a message
60
- const MESSAGE_CONVERSION_ADVOCACY = 64;
61
- // unknown transaction conversion on a message
62
- const MESSAGE_CONVERSION_TRANSACTION = 65;
63
-
64
- const MESSAGE_DELIVERY_FAILURE_SHOULD_RETRY = 66;
65
- const MESSAGE_DELIVERY_FAILURE_SHOULD_NOT_RETRY = 66;
66
-
67
67
  const FORM_ADVOCACY = 66;
68
68
  const FORM_SURVEY = 67;
69
69