@engine9-io/input-tools 1.7.8 → 1.7.9

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/file/tools.js CHANGED
@@ -14,16 +14,9 @@ const unzipper = require('unzipper');
14
14
 
15
15
  const dayjs = require('dayjs');
16
16
 
17
- const {
18
- S3Client,
19
- HeadObjectCommand,
20
- GetObjectCommand,
21
- } = require('@aws-sdk/client-s3');
17
+ const { S3Client, HeadObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
22
18
 
23
-
24
- const {
25
- v7: uuidv7,
26
- } = require('uuid');
19
+ const { v7: uuidv7 } = require('uuid');
27
20
 
28
21
  async function getTempDir({ accountId = 'engine9' }) {
29
22
  const dir = [os.tmpdir(), accountId, new Date().toISOString().substring(0, 10)].join(path.sep);
@@ -52,7 +45,10 @@ async function getTempFilename(options) {
52
45
  }
53
46
 
54
47
  // make a distinct directory, so we don't overwrite the file
55
- dir = `${dir}/${new Date().toISOString().slice(0, -6).replace(/[^0-9]/g, '_')}`;
48
+ dir = `${dir}/${new Date()
49
+ .toISOString()
50
+ .slice(0, -6)
51
+ .replace(/[^0-9]/g, '_')}`;
56
52
 
57
53
  const newDir = await mkdirp(dir);
58
54
 
@@ -97,8 +93,8 @@ async function getPacketFiles({ packet }) {
97
93
  const info = await s3Client.send(
98
94
  new HeadObjectCommand({
99
95
  Bucket,
100
- Key,
101
- }),
96
+ Key
97
+ })
102
98
  );
103
99
  size = info.ContentLength;
104
100
  progress(`Retrieving file of size ${size / (1024 * 1024)} MB`);
@@ -107,13 +103,14 @@ async function getPacketFiles({ packet }) {
107
103
 
108
104
  stream(offset, length) {
109
105
  const ptStream = new PassThrough();
110
- s3Client.send(
111
- new GetObjectCommand({
112
- Bucket,
113
- Key,
114
- Range: `bytes=${offset}-${length ?? ''}`,
115
- }),
116
- )
106
+ s3Client
107
+ .send(
108
+ new GetObjectCommand({
109
+ Bucket,
110
+ Key,
111
+ Range: `bytes=${offset}-${length ?? ''}`
112
+ })
113
+ )
117
114
  .then((response) => {
118
115
  response.Body.pipe(ptStream);
119
116
  })
@@ -122,7 +119,7 @@ async function getPacketFiles({ packet }) {
122
119
  });
123
120
 
124
121
  return ptStream;
125
- },
122
+ }
126
123
  });
127
124
 
128
125
  return directory;
@@ -131,7 +128,6 @@ async function getPacketFiles({ packet }) {
131
128
  return directory;
132
129
  }
133
130
 
134
-
135
131
  async function getManifest({ packet }) {
136
132
  if (!packet) throw new Error('no packet option specififed');
137
133
  const { files } = await getPacketFiles({ packet });
@@ -156,8 +152,8 @@ function getBatchTransform({ batchSize = 100 }) {
156
152
  flush(cb) {
157
153
  if (this.buffer?.length > 0) this.push(this.buffer);
158
154
  cb();
159
- },
160
- }),
155
+ }
156
+ })
161
157
  };
162
158
  }
163
159
  function getDebatchTransform() {
@@ -167,8 +163,8 @@ function getDebatchTransform() {
167
163
  transform(chunk, encoding, cb) {
168
164
  chunk.forEach((c) => this.push(c));
169
165
  cb();
170
- },
171
- }),
166
+ }
167
+ })
172
168
  };
173
169
  }
174
170
 
@@ -218,7 +214,8 @@ async function downloadFile({ packet, type = 'person' }) {
218
214
  const filename = await getTempFilename({ targetFilename: filePath.split('/').pop() });
219
215
 
220
216
  return new Promise((resolve, reject) => {
221
- fileStream.pipe(fs.createWriteStream(filename))
217
+ fileStream
218
+ .pipe(fs.createWriteStream(filename))
222
219
  .on('error', reject)
223
220
  .on('finish', () => {
224
221
  resolve({ filename });
@@ -228,12 +225,12 @@ async function downloadFile({ packet, type = 'person' }) {
228
225
 
229
226
  function isValidDate(d) {
230
227
  // we WANT to use isNaN, not the Number.isNaN -- we're checking the date type
231
- // eslint-disable-next-line no-restricted-globals
228
+
232
229
  return d instanceof Date && !isNaN(d);
233
230
  }
234
231
 
235
232
  function bool(x, _defaultVal) {
236
- const defaultVal = (_defaultVal === undefined) ? false : _defaultVal;
233
+ const defaultVal = _defaultVal === undefined ? false : _defaultVal;
237
234
  if (x === undefined || x === null || x === '') return defaultVal;
238
235
  if (typeof x !== 'string') return !!x;
239
236
  if (x === '1') return true; // 0 will return false, but '1' is true
@@ -255,7 +252,7 @@ function relativeDate(s, _initialDate) {
255
252
  if (!s || s === 'none') return null;
256
253
  if (typeof s.getMonth === 'function') return s;
257
254
  // We actually want a double equals here to test strings as well
258
- // eslint-disable-next-line eqeqeq
255
+
259
256
  if (parseInt(s, 10) == s) {
260
257
  const r = new Date(parseInt(s, 10));
261
258
  if (!isValidDate(r)) throw new Error(`Invalid integer date:${s}`);
@@ -274,15 +271,31 @@ function relativeDate(s, _initialDate) {
274
271
  let period = null;
275
272
  switch (r[3]) {
276
273
  case 'Y':
277
- case 'y': period = 'years'; break;
278
-
279
- case 'M': period = 'months'; break;
280
- case 'w': period = 'weeks'; break;
281
- case 'd': period = 'days'; break;
282
- case 'h': period = 'hours'; break;
283
- case 'm': period = 'minutes'; break;
284
- case 's': period = 'seconds'; break;
285
- default: period = 'minutes'; break;
274
+ case 'y':
275
+ period = 'years';
276
+ break;
277
+
278
+ case 'M':
279
+ period = 'months';
280
+ break;
281
+ case 'w':
282
+ period = 'weeks';
283
+ break;
284
+ case 'd':
285
+ period = 'days';
286
+ break;
287
+ case 'h':
288
+ period = 'hours';
289
+ break;
290
+ case 'm':
291
+ period = 'minutes';
292
+ break;
293
+ case 's':
294
+ period = 'seconds';
295
+ break;
296
+ default:
297
+ period = 'minutes';
298
+ break;
286
299
  }
287
300
 
288
301
  let d = dayjs(initialDate);
@@ -317,12 +330,29 @@ function relativeDate(s, _initialDate) {
317
330
  */
318
331
  function makeStrings(o) {
319
332
  return Object.entries(o).reduce((a, [k, v]) => {
320
- a[k] = (typeof v === 'object') ? JSON.stringify(v) : String(v);
333
+ a[k] = typeof v === 'object' ? JSON.stringify(v) : String(v);
321
334
  return a;
322
335
  }, {});
323
336
  }
337
+ function appendPostfix(filename, postfix) {
338
+ const filenameParts = filename.split('/');
339
+ const fileParts = filenameParts
340
+ .slice(-1)[0]
341
+ .split('.')
342
+ .filter(Boolean)
343
+ .filter((d) => d !== postfix);
344
+
345
+ let targetFile = null;
346
+ if (fileParts.slice(-1)[0] === 'gz') {
347
+ targetFile = fileParts.slice(0, -2).concat(postfix).concat(fileParts.slice(-2)).join('.');
348
+ } else {
349
+ targetFile = fileParts.slice(0, -1).concat(postfix).concat(fileParts.slice(-1)).join('.');
350
+ }
351
+ return filenameParts.slice(0, -1).concat(targetFile).join('/');
352
+ }
324
353
 
325
354
  module.exports = {
355
+ appendPostfix,
326
356
  bool,
327
357
  downloadFile,
328
358
  getTempFilename,
@@ -336,5 +366,5 @@ module.exports = {
336
366
  makeStrings,
337
367
  relativeDate,
338
368
  streamPacket,
339
- writeTempFile,
369
+ writeTempFile
340
370
  };
package/index.js CHANGED
@@ -6,15 +6,14 @@ const dayjs = require('dayjs');
6
6
  const debug = require('debug')('@engine9/input-tools');
7
7
 
8
8
  const unzipper = require('unzipper');
9
- const {
10
- v4: uuidv4, v5: uuidv5, v7: uuidv7, validate: uuidIsValid,
11
- } = require('uuid');
9
+ const { v4: uuidv4, v5: uuidv5, v7: uuidv7, validate: uuidIsValid } = require('uuid');
12
10
  const archiver = require('archiver');
13
11
  const handlebars = require('handlebars');
14
12
 
15
13
  const FileUtilities = require('./file/FileUtilities');
16
14
 
17
15
  const {
16
+ appendPostfix,
18
17
  bool,
19
18
  getManifest,
20
19
  getFile,
@@ -29,7 +28,7 @@ const {
29
28
  getDebatchTransform,
30
29
  getStringArray,
31
30
  makeStrings,
32
- writeTempFile,
31
+ writeTempFile
33
32
  } = require('./file/tools');
34
33
 
35
34
  const ForEachEntry = require('./ForEachEntry');
@@ -45,7 +44,7 @@ function getFormattedDate(dateObject, format = 'MMM DD,YYYY') {
45
44
 
46
45
  handlebars.registerHelper('date', (d, f) => {
47
46
  let format;
48
- if (typeof f === 'string')format = f;
47
+ if (typeof f === 'string') format = f;
49
48
  return getFormattedDate(d, format);
50
49
  });
51
50
  handlebars.registerHelper('json', (d) => JSON.stringify(d));
@@ -60,11 +59,7 @@ async function list(_path) {
60
59
  const directory = await unzipper.Open.file(_path);
61
60
 
62
61
  return new Promise((resolve, reject) => {
63
- directory.files[0]
64
- .stream()
65
- .pipe(fs.createWriteStream('firstFile'))
66
- .on('error', reject)
67
- .on('finish', resolve);
62
+ directory.files[0].stream().pipe(fs.createWriteStream('firstFile')).on('error', reject).on('finish', resolve);
68
63
  });
69
64
  }
70
65
 
@@ -74,11 +69,7 @@ async function extract(_path, _file) {
74
69
  const file = directory.files.find((d) => d.path === _file);
75
70
  const tempFilename = await getTempFilename({ source: _file });
76
71
  return new Promise((resolve, reject) => {
77
- file
78
- .stream()
79
- .pipe(fs.createWriteStream(tempFilename))
80
- .on('error', reject)
81
- .on('finish', resolve);
72
+ file.stream().pipe(fs.createWriteStream(tempFilename)).on('error', reject).on('finish', resolve);
82
73
  });
83
74
  }
84
75
 
@@ -87,7 +78,7 @@ function appendFiles(existingFiles, _newFiles, options) {
87
78
  if (newFiles.length === 0) return;
88
79
  let { type, dateCreated } = options || {};
89
80
  if (!type) type = 'unknown';
90
- if (!dateCreated)dateCreated = new Date().toISOString();
81
+ if (!dateCreated) dateCreated = new Date().toISOString();
91
82
  let arr = newFiles;
92
83
  if (!Array.isArray(newFiles)) arr = [arr];
93
84
 
@@ -96,7 +87,7 @@ function appendFiles(existingFiles, _newFiles, options) {
96
87
  type,
97
88
  originalFilename: '',
98
89
  isNew: true,
99
- dateCreated,
90
+ dateCreated
100
91
  };
101
92
 
102
93
  if (typeof p === 'string') {
@@ -121,7 +112,7 @@ async function create(options) {
121
112
  messageFiles = [], // file with contents of message, used for delivery
122
113
  personFiles = [], // files with data on people
123
114
  timelineFiles = [], // activity entry
124
- statisticsFiles = [], // files with aggregate statistics
115
+ statisticsFiles = [] // files with aggregate statistics
125
116
  } = options;
126
117
  if (options.peopleFiles) throw new Error('Unknown option: peopleFiles, did you mean personFiles?');
127
118
 
@@ -132,21 +123,21 @@ async function create(options) {
132
123
  appendFiles(files, timelineFiles, { type: 'timeline', dateCreated });
133
124
  appendFiles(files, statisticsFiles, { type: 'statistics', dateCreated });
134
125
 
135
- const zipFilename = target || await getTempFilename({ postfix: '.packet.zip' });
126
+ const zipFilename = target || (await getTempFilename({ postfix: '.packet.zip' }));
136
127
 
137
128
  const manifest = {
138
129
  accountId,
139
130
  source: {
140
- pluginId,
131
+ pluginId
141
132
  },
142
133
  dateCreated,
143
- files,
134
+ files
144
135
  };
145
136
 
146
137
  // create a file to stream archive data to.
147
138
  const output = fs.createWriteStream(zipFilename);
148
139
  const archive = archiver('zip', {
149
- zlib: { level: 9 }, // Sets the compression level.
140
+ zlib: { level: 9 } // Sets the compression level.
150
141
  });
151
142
  return new Promise((resolve, reject) => {
152
143
  debug(`Setting up write stream to ${zipFilename}`);
@@ -157,7 +148,7 @@ async function create(options) {
157
148
  debug(zipFilename);
158
149
  return resolve({
159
150
  filename: zipFilename,
160
- bytes: archive.pointer(),
151
+ bytes: archive.pointer()
161
152
  });
162
153
  });
163
154
 
@@ -196,7 +187,6 @@ function intToByteArray(_v) {
196
187
  const byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
197
188
  let v = _v;
198
189
  for (let index = 0; index < byteArray.length; index += 1) {
199
- // eslint-disable-next-line no-bitwise
200
190
  const byte = v & 0xff;
201
191
  byteArray[index] = byte;
202
192
  v = (v - byte) / 256;
@@ -226,23 +216,26 @@ function getInputUUID(a, b) {
226
216
  return uuidv5(`${pluginId}:${rid}`, '3d0e5d99-6ba9-4fab-9bb2-c32304d3df8e');
227
217
  }
228
218
 
229
- function getUUIDv7(date, inputUuid) { /* optional date and input UUID */
219
+ function getUUIDv7(date, inputUuid) {
220
+ /* optional date and input UUID */
230
221
  const uuid = inputUuid || uuidv7();
231
222
  const bytes = Buffer.from(uuid.replace(/-/g, ''), 'hex');
232
223
  if (date !== undefined) {
233
224
  const d = new Date(date);
234
225
  // isNaN behaves differently than Number.isNaN -- we're actually going for the
235
226
  // attempted conversion here
236
- // eslint-disable-next-line no-restricted-globals
227
+
237
228
  if (isNaN(d)) throw new Error(`getUUIDv7 got an invalid date:${date || '<blank>'}`);
238
229
  const dateBytes = intToByteArray(d.getTime()).reverse();
239
- dateBytes.slice(2, 8).forEach((b, i) => { bytes[i] = b; });
230
+ dateBytes.slice(2, 8).forEach((b, i) => {
231
+ bytes[i] = b;
232
+ });
240
233
  }
241
234
  return uuidv4({ random: bytes });
242
235
  }
243
236
  /* Returns a date from a given uuid (assumed to be a v7, otherwise the results are ... weird */
244
237
  function getUUIDTimestamp(uuid) {
245
- const ts = parseInt((`${uuid}`).replace(/-/g, '').slice(0, 12), 16);
238
+ const ts = parseInt(`${uuid}`.replace(/-/g, '').slice(0, 12), 16);
246
239
  return new Date(ts);
247
240
  }
248
241
 
@@ -266,7 +259,8 @@ function getTimelineEntryUUID(inputObject, { defaults = {} } = {}) {
266
259
 
267
260
  if (o.remote_entry_id) {
268
261
  // get a temp ID
269
- if (!o.input_id) throw new Error('Error generating timeline entry uuid -- remote_entry_id specified, but no input_id');
262
+ if (!o.input_id)
263
+ throw new Error('Error generating timeline entry uuid -- remote_entry_id specified, but no input_id');
270
264
  const uuid = uuidv5(o.remote_entry_id, o.input_id);
271
265
  // Change out the ts to match the v7 sorting.
272
266
  // But because outside specified remote_entry_uuid
@@ -274,14 +268,13 @@ function getTimelineEntryUUID(inputObject, { defaults = {} } = {}) {
274
268
  return getUUIDv7(o.ts, uuid);
275
269
  }
276
270
 
277
- const missing = requiredTimelineEntryFields
278
- .filter((d) => o[d] === undefined);// 0 could be an entry type value
271
+ const missing = requiredTimelineEntryFields.filter((d) => o[d] === undefined); // 0 could be an entry type value
279
272
 
280
273
  if (missing.length > 0) throw new Error(`Missing required fields to append an entry_id:${missing.join(',')}`);
281
274
  const ts = new Date(o.ts);
282
275
  // isNaN behaves differently than Number.isNaN -- we're actually going for the
283
276
  // attempted conversion here
284
- // eslint-disable-next-line no-restricted-globals
277
+
285
278
  if (isNaN(ts)) throw new Error(`getTimelineEntryUUID got an invalid date:${o.ts || '<blank>'}`);
286
279
  const idString = `${ts.toISOString()}-${o.person_id}-${o.entry_type_id}-${o.source_code_id || 0}`;
287
280
 
@@ -308,6 +301,7 @@ function getEntryTypeId(o, { defaults = {} } = {}) {
308
301
  }
309
302
 
310
303
  module.exports = {
304
+ appendPostfix,
311
305
  bool,
312
306
  create,
313
307
  list,
@@ -339,5 +333,5 @@ module.exports = {
339
333
  uuidIsValid,
340
334
  uuidv4,
341
335
  uuidv5,
342
- uuidv7,
336
+ uuidv7
343
337
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engine9-io/input-tools",
3
- "version": "1.7.8",
3
+ "version": "1.7.9",
4
4
  "description": "Tools for dealing with Engine9 inputs",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,55 @@
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
+ assert.equal(counter, 1000, `Expected to loop through 1000 people, actual:${counter}`);
53
+ });
54
+ debug('Completed tests');
55
+ });
@@ -1,63 +0,0 @@
1
- const {
2
- describe, it,
3
- } = require('node:test');
4
- const assert = require('node:assert');
5
- const debug = require('debug')('test/forEach');
6
-
7
- const { ForEachEntry } = require('../../index');
8
-
9
- describe('Test Person Packet For Each', async () => {
10
- it('forEachPerson Should loop through 1000 sample people', async () => {
11
- let counter = 0;
12
- const forEach = new ForEachEntry();
13
- const result = await forEach.process(
14
- {
15
- packet: 'test/sample/1000_message.packet.zip',
16
- batchSize: 50,
17
- bindings: {
18
- timelineOutputFileStream: {
19
- path: 'output.timeline',
20
- options: {
21
- entry_type: 'ENTRY_OPTION',
22
- },
23
- },
24
- sampleOutputFileStream: {
25
- path: 'output.stream',
26
- },
27
- },
28
- async transform(props) {
29
- const {
30
- batch,
31
- timelineOutputFileStream,
32
- sampleOutputFileStream,
33
- } = props;
34
-
35
- batch.forEach((p) => {
36
- if (Math.random() > 0.9) {
37
- sampleOutputFileStream.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: 'SAMPLE_OUTPUT',
42
- });
43
- }
44
- timelineOutputFileStream.push(
45
- {
46
- // for testing we don't need real person_ids
47
- person_id: p.person_id || Math.floor(Math.random() * 1000000),
48
- email: p.email,
49
- entry_type: 'EMAIL_DELIVERED',
50
- },
51
- );
52
- });
53
-
54
- batch.forEach(() => { counter += 1; });
55
- },
56
- },
57
- );
58
- assert(result.outputFiles?.timelineOutputFileStream?.[0]?.records);
59
- assert(result.outputFiles?.sampleOutputFileStream?.[0]?.records);
60
- assert.equal(counter, 1000, `Expected to loop through 1000 people, actual:${counter}`);
61
- });
62
- debug('Completed tests');
63
- });
File without changes
File without changes
File without changes