@engine9-io/input-tools 1.9.11 → 2.0.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/ForEachEntry.js +18 -45
- package/ValidatingReadable.js +3 -6
- package/buildSamplePackets.js +11 -16
- package/eslint.config.mjs +15 -11
- package/file/FileUtilities.js +29 -153
- package/file/GoogleDrive.js +32 -38
- package/file/Parquet.js +112 -124
- package/file/R2.js +27 -32
- package/file/S3.js +259 -293
- package/file/tools.js +33 -54
- package/index.js +59 -74
- package/package.json +2 -1
- package/test/cli.js +3 -4
- package/test/file.js +6 -7
- package/test/processing/bigDataMessage.js +8 -10
- package/test/processing/forEach.js +6 -8
- package/test/processing/forEachResume.js +6 -8
- package/test/processing/message.js +31 -39
- package/test/processing/zip.js +6 -7
- package/test/uuid.js +6 -11
- package/timelineTypes.js +2 -24
package/file/tools.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import debug$0 from 'debug';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { mkdirp } from 'mkdirp';
|
|
6
|
+
import nodestream from 'node:stream';
|
|
7
|
+
import JSON5 from 'json5';
|
|
8
|
+
import unzipper from 'unzipper';
|
|
9
|
+
import dayjs from 'dayjs';
|
|
10
|
+
import clientS3 from '@aws-sdk/client-s3';
|
|
11
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
2
12
|
|
|
3
13
|
const fsp = fs.promises;
|
|
4
|
-
const
|
|
5
|
-
const debug = require('debug')('@engine9/input-tools');
|
|
6
|
-
const os = require('node:os');
|
|
7
|
-
const { mkdirp } = require('mkdirp');
|
|
8
|
-
const { Transform } = require('node:stream');
|
|
14
|
+
const debug = debug$0('@engine9/input-tools');
|
|
9
15
|
|
|
10
|
-
const
|
|
11
|
-
const { PassThrough } =
|
|
12
|
-
const progress =
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const dayjs = require('dayjs');
|
|
16
|
-
|
|
17
|
-
const { S3Client, HeadObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
|
|
18
|
-
|
|
19
|
-
const { v7: uuidv7 } = require('uuid');
|
|
16
|
+
const { Transform } = nodestream;
|
|
17
|
+
const { PassThrough } = nodestream;
|
|
18
|
+
const progress = debug$0('info:@engine9/input-tools');
|
|
19
|
+
const { S3Client, HeadObjectCommand, GetObjectCommand } = clientS3;
|
|
20
20
|
|
|
21
21
|
async function getTempDir({ accountId = 'engine9' }) {
|
|
22
22
|
const dir = [os.tmpdir(), accountId, new Date().toISOString().substring(0, 10)].join(path.sep);
|
|
@@ -27,7 +27,6 @@ async function getTempDir({ accountId = 'engine9' }) {
|
|
|
27
27
|
}
|
|
28
28
|
return dir;
|
|
29
29
|
}
|
|
30
|
-
|
|
31
30
|
/*
|
|
32
31
|
Get a new, timestamp based filename, creating any necessary directories
|
|
33
32
|
options:
|
|
@@ -36,22 +35,18 @@ async function getTempDir({ accountId = 'engine9' }) {
|
|
|
36
35
|
*/
|
|
37
36
|
async function getTempFilename(options) {
|
|
38
37
|
let dir = await getTempDir(options);
|
|
39
|
-
|
|
40
38
|
const target = options.targetFilename;
|
|
41
39
|
if (target) {
|
|
42
40
|
if (target.indexOf('/') === 0 || target.indexOf('\\') === 0) {
|
|
43
41
|
// assume a full directory path has been specified
|
|
44
42
|
return target;
|
|
45
43
|
}
|
|
46
|
-
|
|
47
44
|
// make a distinct directory, so we don't overwrite the file
|
|
48
45
|
dir = `${dir}/${new Date()
|
|
49
46
|
.toISOString()
|
|
50
47
|
.slice(0, -6)
|
|
51
48
|
.replace(/[^0-9]/g, '_')}`;
|
|
52
|
-
|
|
53
49
|
const newDir = await mkdirp(dir);
|
|
54
|
-
|
|
55
50
|
return `${newDir}/${target}`;
|
|
56
51
|
}
|
|
57
52
|
let { prefix } = options;
|
|
@@ -62,30 +57,23 @@ async function getTempFilename(options) {
|
|
|
62
57
|
postfix = `_${options.source.split('/').pop()}`;
|
|
63
58
|
postfix = postfix.replace(/['"\\]/g, '').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
64
59
|
}
|
|
65
|
-
|
|
66
60
|
if (prefix) prefix += '_';
|
|
67
|
-
|
|
68
61
|
const p = `${dir}/${prefix || ''}${uuidv7()}${postfix || '.txt'}`;
|
|
69
62
|
return p;
|
|
70
63
|
}
|
|
71
|
-
|
|
72
64
|
async function writeTempFile(options) {
|
|
73
65
|
const { content, postfix = '.txt' } = options;
|
|
74
66
|
const filename = await getTempFilename({ ...options, postfix });
|
|
75
|
-
|
|
76
67
|
await fsp.writeFile(filename, content);
|
|
77
68
|
return { filename };
|
|
78
69
|
}
|
|
79
|
-
|
|
80
70
|
async function getPacketFiles({ packet }) {
|
|
81
71
|
if (packet.indexOf('s3://') === 0) {
|
|
82
72
|
const parts = packet.split('/');
|
|
83
73
|
const Bucket = parts[2];
|
|
84
74
|
const Key = parts.slice(3).join('/');
|
|
85
75
|
const s3Client = new S3Client({});
|
|
86
|
-
|
|
87
76
|
debug('Getting ', { Bucket, Key });
|
|
88
|
-
|
|
89
77
|
// const directory = await unzipper.Open.s3(s3Client, { Bucket, Key });
|
|
90
78
|
let size = null;
|
|
91
79
|
const directory = await unzipper.Open.custom({
|
|
@@ -100,7 +88,6 @@ async function getPacketFiles({ packet }) {
|
|
|
100
88
|
progress(`Retrieving file of size ${size / (1024 * 1024)} MB`);
|
|
101
89
|
return info.ContentLength;
|
|
102
90
|
},
|
|
103
|
-
|
|
104
91
|
stream(offset, length) {
|
|
105
92
|
const ptStream = new PassThrough();
|
|
106
93
|
s3Client
|
|
@@ -117,17 +104,14 @@ async function getPacketFiles({ packet }) {
|
|
|
117
104
|
.catch((error) => {
|
|
118
105
|
ptStream.emit('error', error);
|
|
119
106
|
});
|
|
120
|
-
|
|
121
107
|
return ptStream;
|
|
122
108
|
}
|
|
123
109
|
});
|
|
124
|
-
|
|
125
110
|
return directory;
|
|
126
111
|
}
|
|
127
112
|
const directory = await unzipper.Open.file(packet);
|
|
128
113
|
return directory;
|
|
129
114
|
}
|
|
130
|
-
|
|
131
115
|
async function getManifest({ packet }) {
|
|
132
116
|
if (!packet) throw new Error('no packet option specififed');
|
|
133
117
|
const { files } = await getPacketFiles({ packet });
|
|
@@ -136,7 +120,6 @@ async function getManifest({ packet }) {
|
|
|
136
120
|
const manifest = JSON.parse(content.toString());
|
|
137
121
|
return manifest;
|
|
138
122
|
}
|
|
139
|
-
|
|
140
123
|
function getBatchTransform({ batchSize = 100 }) {
|
|
141
124
|
return {
|
|
142
125
|
transform: new Transform({
|
|
@@ -167,7 +150,6 @@ function getDebatchTransform() {
|
|
|
167
150
|
})
|
|
168
151
|
};
|
|
169
152
|
}
|
|
170
|
-
|
|
171
153
|
async function getFile({ filename, packet, type }) {
|
|
172
154
|
if (!packet && !filename) throw new Error('no packet option specififed');
|
|
173
155
|
let content = null;
|
|
@@ -196,7 +178,6 @@ async function getFile({ filename, packet, type }) {
|
|
|
196
178
|
}
|
|
197
179
|
return content;
|
|
198
180
|
}
|
|
199
|
-
|
|
200
181
|
async function streamPacket({ packet, type }) {
|
|
201
182
|
if (!packet) throw new Error('no packet option specififed');
|
|
202
183
|
const manifest = await getManifest({ packet });
|
|
@@ -208,11 +189,9 @@ async function streamPacket({ packet, type }) {
|
|
|
208
189
|
const handle = files.find((d) => d.path === filePath);
|
|
209
190
|
return { stream: handle.stream(), path: filePath };
|
|
210
191
|
}
|
|
211
|
-
|
|
212
192
|
async function downloadFile({ packet, type = 'person' }) {
|
|
213
193
|
const { stream: fileStream, path: filePath } = await streamPacket({ packet, type });
|
|
214
194
|
const filename = await getTempFilename({ targetFilename: filePath.split('/').pop() });
|
|
215
|
-
|
|
216
195
|
return new Promise((resolve, reject) => {
|
|
217
196
|
fileStream
|
|
218
197
|
.pipe(fs.createWriteStream(filename))
|
|
@@ -222,13 +201,10 @@ async function downloadFile({ packet, type = 'person' }) {
|
|
|
222
201
|
});
|
|
223
202
|
});
|
|
224
203
|
}
|
|
225
|
-
|
|
226
204
|
function isValidDate(d) {
|
|
227
205
|
// we WANT to use isNaN, not the Number.isNaN -- we're checking the date type
|
|
228
|
-
|
|
229
206
|
return d instanceof Date && !isNaN(d);
|
|
230
207
|
}
|
|
231
|
-
|
|
232
208
|
function bool(x, _defaultVal) {
|
|
233
209
|
const defaultVal = _defaultVal === undefined ? false : _defaultVal;
|
|
234
210
|
if (x === undefined || x === null || x === '') return defaultVal;
|
|
@@ -241,7 +217,6 @@ function getStringArray(s, nonZeroLength) {
|
|
|
241
217
|
let a = s || [];
|
|
242
218
|
if (typeof a === 'number') a = String(a);
|
|
243
219
|
if (typeof a === 'string') a = [a];
|
|
244
|
-
|
|
245
220
|
if (typeof s === 'string') a = s.split(',');
|
|
246
221
|
a = a.map((x) => x.toString().trim()).filter(Boolean);
|
|
247
222
|
if (nonZeroLength && a.length === 0) a = [0];
|
|
@@ -252,21 +227,17 @@ function relativeDate(s, _initialDate) {
|
|
|
252
227
|
if (!s || s === 'none') return null;
|
|
253
228
|
if (typeof s.getMonth === 'function') return s;
|
|
254
229
|
// We actually want a double equals here to test strings as well
|
|
255
|
-
|
|
256
230
|
if (parseInt(s, 10) == s) {
|
|
257
231
|
const r = new Date(parseInt(s, 10));
|
|
258
232
|
if (!isValidDate(r)) throw new Error(`Invalid integer date:${s}`);
|
|
259
233
|
return r;
|
|
260
234
|
}
|
|
261
|
-
|
|
262
235
|
if (initialDate) {
|
|
263
236
|
initialDate = new Date(initialDate);
|
|
264
237
|
} else {
|
|
265
238
|
initialDate = new Date();
|
|
266
239
|
}
|
|
267
|
-
|
|
268
240
|
let r = s.match(/^([+-]{1})([0-9]+)([YyMwdhms]{1})([.a-z]*)$/);
|
|
269
|
-
|
|
270
241
|
if (r) {
|
|
271
242
|
let period = null;
|
|
272
243
|
switch (r[3]) {
|
|
@@ -274,7 +245,6 @@ function relativeDate(s, _initialDate) {
|
|
|
274
245
|
case 'y':
|
|
275
246
|
period = 'years';
|
|
276
247
|
break;
|
|
277
|
-
|
|
278
248
|
case 'M':
|
|
279
249
|
period = 'months';
|
|
280
250
|
break;
|
|
@@ -297,9 +267,7 @@ function relativeDate(s, _initialDate) {
|
|
|
297
267
|
period = 'minutes';
|
|
298
268
|
break;
|
|
299
269
|
}
|
|
300
|
-
|
|
301
270
|
let d = dayjs(initialDate);
|
|
302
|
-
|
|
303
271
|
if (r[1] === '+') {
|
|
304
272
|
d = d.add(parseInt(r[2], 10), period);
|
|
305
273
|
} else {
|
|
@@ -312,7 +280,6 @@ function relativeDate(s, _initialDate) {
|
|
|
312
280
|
else if (opts[0] === 'end') d = d.endOf(opts[1] || 'day');
|
|
313
281
|
else throw new Error(`Invalid relative date,unknown options:${r[4]}`);
|
|
314
282
|
}
|
|
315
|
-
|
|
316
283
|
return d.toDate();
|
|
317
284
|
}
|
|
318
285
|
if (s === 'now') {
|
|
@@ -323,7 +290,6 @@ function relativeDate(s, _initialDate) {
|
|
|
323
290
|
if (!isValidDate(r)) throw new Error(`Invalid Date: ${s}`);
|
|
324
291
|
return r;
|
|
325
292
|
}
|
|
326
|
-
|
|
327
293
|
/*
|
|
328
294
|
When comparing two objects, some may come from a file (thus strings), and some from
|
|
329
295
|
a database or elsewhere (not strings), so for deduping make sure to make them all strings
|
|
@@ -341,7 +307,6 @@ function appendPostfix(filename, postfix) {
|
|
|
341
307
|
.split('.')
|
|
342
308
|
.filter(Boolean)
|
|
343
309
|
.filter((d) => d !== postfix);
|
|
344
|
-
|
|
345
310
|
let targetFile = null;
|
|
346
311
|
if (fileParts.slice(-1)[0] === 'gz') {
|
|
347
312
|
targetFile = fileParts.slice(0, -2).concat(postfix).concat(fileParts.slice(-2)).join('.');
|
|
@@ -350,7 +315,6 @@ function appendPostfix(filename, postfix) {
|
|
|
350
315
|
}
|
|
351
316
|
return filenameParts.slice(0, -1).concat(targetFile).join('/');
|
|
352
317
|
}
|
|
353
|
-
|
|
354
318
|
function parseJSON5(o, defaultVal) {
|
|
355
319
|
if (o) {
|
|
356
320
|
if (typeof o === 'object') return o;
|
|
@@ -359,8 +323,23 @@ function parseJSON5(o, defaultVal) {
|
|
|
359
323
|
}
|
|
360
324
|
return defaultVal || o;
|
|
361
325
|
}
|
|
362
|
-
|
|
363
|
-
|
|
326
|
+
export { appendPostfix };
|
|
327
|
+
export { bool };
|
|
328
|
+
export { downloadFile };
|
|
329
|
+
export { getTempFilename };
|
|
330
|
+
export { getTempDir };
|
|
331
|
+
export { getBatchTransform };
|
|
332
|
+
export { getDebatchTransform };
|
|
333
|
+
export { getFile };
|
|
334
|
+
export { getManifest };
|
|
335
|
+
export { getPacketFiles };
|
|
336
|
+
export { getStringArray };
|
|
337
|
+
export { makeStrings };
|
|
338
|
+
export { parseJSON5 };
|
|
339
|
+
export { relativeDate };
|
|
340
|
+
export { streamPacket };
|
|
341
|
+
export { writeTempFile };
|
|
342
|
+
export default {
|
|
364
343
|
appendPostfix,
|
|
365
344
|
bool,
|
|
366
345
|
downloadFile,
|
package/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import debug$0 from 'debug';
|
|
5
|
+
import unzipper from 'unzipper';
|
|
6
|
+
import { v4 as uuidv4, v5 as uuidv5, v7 as uuidv7, validate as uuidIsValid } from 'uuid';
|
|
7
|
+
import archiver from 'archiver';
|
|
8
|
+
import handlebars from 'handlebars';
|
|
9
|
+
import FileUtilities from './file/FileUtilities.js';
|
|
10
|
+
import tools from './file/tools.js';
|
|
11
|
+
import ForEachEntry from './ForEachEntry.js';
|
|
12
|
+
import { TIMELINE_ENTRY_TYPES } from './timelineTypes.js';
|
|
13
|
+
const debug = debug$0('@engine9/input-tools');
|
|
14
14
|
|
|
15
15
|
const {
|
|
16
16
|
appendPostfix,
|
|
@@ -30,40 +30,28 @@ const {
|
|
|
30
30
|
streamPacket,
|
|
31
31
|
makeStrings,
|
|
32
32
|
writeTempFile
|
|
33
|
-
} =
|
|
34
|
-
|
|
35
|
-
const ForEachEntry = require('./ForEachEntry');
|
|
36
|
-
|
|
37
|
-
const { TIMELINE_ENTRY_TYPES } = require('./timelineTypes');
|
|
38
|
-
|
|
33
|
+
} = tools;
|
|
39
34
|
function getFormattedDate(dateObject, format = 'MMM DD,YYYY') {
|
|
40
35
|
let d = dateObject;
|
|
41
36
|
if (d === 'now') d = new Date();
|
|
42
37
|
if (d) return dayjs(d).format(format);
|
|
43
38
|
return '';
|
|
44
39
|
}
|
|
45
|
-
|
|
46
40
|
handlebars.registerHelper('date', (d, f) => {
|
|
47
41
|
let format;
|
|
48
42
|
if (typeof f === 'string') format = f;
|
|
49
43
|
return getFormattedDate(d, format);
|
|
50
44
|
});
|
|
51
45
|
handlebars.registerHelper('json', (d) => JSON.stringify(d));
|
|
52
|
-
|
|
53
46
|
handlebars.registerHelper('uuid', () => uuidv7());
|
|
54
|
-
|
|
55
47
|
handlebars.registerHelper('percent', (a, b) => `${((100 * a) / b).toFixed(2)}%`);
|
|
56
|
-
|
|
57
48
|
handlebars.registerHelper('or', (a, b, c) => a || b || c);
|
|
58
|
-
|
|
59
49
|
async function list(_path) {
|
|
60
50
|
const directory = await unzipper.Open.file(_path);
|
|
61
|
-
|
|
62
51
|
return new Promise((resolve, reject) => {
|
|
63
52
|
directory.files[0].stream().pipe(fs.createWriteStream('firstFile')).on('error', reject).on('finish', resolve);
|
|
64
53
|
});
|
|
65
54
|
}
|
|
66
|
-
|
|
67
55
|
async function extract(_path, _file) {
|
|
68
56
|
const directory = await unzipper.Open(_path);
|
|
69
57
|
// return directory.files.map((f) => f.path);
|
|
@@ -73,7 +61,6 @@ async function extract(_path, _file) {
|
|
|
73
61
|
file.stream().pipe(fs.createWriteStream(tempFilename)).on('error', reject).on('finish', resolve);
|
|
74
62
|
});
|
|
75
63
|
}
|
|
76
|
-
|
|
77
64
|
function appendFiles(existingFiles, _newFiles, options) {
|
|
78
65
|
const newFiles = getStringArray(_newFiles);
|
|
79
66
|
if (newFiles.length === 0) return;
|
|
@@ -82,7 +69,6 @@ function appendFiles(existingFiles, _newFiles, options) {
|
|
|
82
69
|
if (!dateCreated) dateCreated = new Date().toISOString();
|
|
83
70
|
let arr = newFiles;
|
|
84
71
|
if (!Array.isArray(newFiles)) arr = [arr];
|
|
85
|
-
|
|
86
72
|
arr.forEach((p) => {
|
|
87
73
|
const item = {
|
|
88
74
|
type,
|
|
@@ -90,13 +76,11 @@ function appendFiles(existingFiles, _newFiles, options) {
|
|
|
90
76
|
isNew: true,
|
|
91
77
|
dateCreated
|
|
92
78
|
};
|
|
93
|
-
|
|
94
79
|
if (typeof p === 'string') {
|
|
95
80
|
item.originalFilename = path.resolve(process.cwd(), p);
|
|
96
81
|
} else {
|
|
97
82
|
item.originalFilename = path.resolve(process.cwd(), item.originalFilename);
|
|
98
83
|
}
|
|
99
|
-
|
|
100
84
|
const file = item.originalFilename.split(path.sep).pop();
|
|
101
85
|
item.path = `${type}/${file}`;
|
|
102
86
|
const existingFile = existingFiles.find((f) => f.path === item.path);
|
|
@@ -104,7 +88,6 @@ function appendFiles(existingFiles, _newFiles, options) {
|
|
|
104
88
|
existingFiles.push(item);
|
|
105
89
|
});
|
|
106
90
|
}
|
|
107
|
-
|
|
108
91
|
async function create(options) {
|
|
109
92
|
const {
|
|
110
93
|
accountId = 'engine9',
|
|
@@ -116,16 +99,13 @@ async function create(options) {
|
|
|
116
99
|
statisticsFiles = [] // files with aggregate statistics
|
|
117
100
|
} = options;
|
|
118
101
|
if (options.peopleFiles) throw new Error('Unknown option: peopleFiles, did you mean personFiles?');
|
|
119
|
-
|
|
120
102
|
const files = [];
|
|
121
103
|
const dateCreated = new Date().toISOString();
|
|
122
104
|
appendFiles(files, messageFiles, { type: 'message', dateCreated });
|
|
123
105
|
appendFiles(files, personFiles, { type: 'person', dateCreated });
|
|
124
106
|
appendFiles(files, timelineFiles, { type: 'timeline', dateCreated });
|
|
125
107
|
appendFiles(files, statisticsFiles, { type: 'statistics', dateCreated });
|
|
126
|
-
|
|
127
108
|
const zipFilename = target || (await getTempFilename({ postfix: '.packet.zip' }));
|
|
128
|
-
|
|
129
109
|
const manifest = {
|
|
130
110
|
accountId,
|
|
131
111
|
source: {
|
|
@@ -134,7 +114,6 @@ async function create(options) {
|
|
|
134
114
|
dateCreated,
|
|
135
115
|
files
|
|
136
116
|
};
|
|
137
|
-
|
|
138
117
|
// create a file to stream archive data to.
|
|
139
118
|
const output = fs.createWriteStream(zipFilename);
|
|
140
119
|
const archive = archiver('zip', {
|
|
@@ -152,37 +131,30 @@ async function create(options) {
|
|
|
152
131
|
bytes: archive.pointer()
|
|
153
132
|
});
|
|
154
133
|
});
|
|
155
|
-
|
|
156
134
|
// This event is fired when the data source is drained no matter what was the data source.
|
|
157
135
|
// It is not part of this library but rather from the NodeJS Stream API.
|
|
158
136
|
// @see: https://nodejs.org/api/stream.html#stream_event_end
|
|
159
137
|
output.on('end', () => {
|
|
160
138
|
// debug('end event -- Data has been drained');
|
|
161
139
|
});
|
|
162
|
-
|
|
163
140
|
// warnings could be file not founds, etc, but we error even on those
|
|
164
141
|
archive.on('warning', (err) => {
|
|
165
142
|
reject(err);
|
|
166
143
|
});
|
|
167
|
-
|
|
168
144
|
// good practice to catch this error explicitly
|
|
169
145
|
archive.on('error', (err) => {
|
|
170
146
|
reject(err);
|
|
171
147
|
});
|
|
172
|
-
|
|
173
148
|
archive.pipe(output);
|
|
174
|
-
|
|
175
149
|
files.forEach(({ path: name, originalFilename }) => archive.file(originalFilename, { name }));
|
|
176
150
|
files.forEach((f) => {
|
|
177
151
|
delete f.originalFilename;
|
|
178
152
|
delete f.isNew;
|
|
179
153
|
});
|
|
180
|
-
|
|
181
154
|
archive.append(Buffer.from(JSON.stringify(manifest, null, 4), 'utf8'), { name: 'manifest.json' });
|
|
182
155
|
archive.finalize();
|
|
183
156
|
});
|
|
184
157
|
}
|
|
185
|
-
|
|
186
158
|
function intToByteArray(_v) {
|
|
187
159
|
// we want to represent the input as a 8-bytes array
|
|
188
160
|
const byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
@@ -192,14 +164,12 @@ function intToByteArray(_v) {
|
|
|
192
164
|
byteArray[index] = byte;
|
|
193
165
|
v = (v - byte) / 256;
|
|
194
166
|
}
|
|
195
|
-
|
|
196
167
|
return byteArray;
|
|
197
168
|
}
|
|
198
169
|
function getPluginUUID(uniqueNamespaceLikeDomainName, valueWithinNamespace) {
|
|
199
170
|
// Random custom namespace for plugins -- not secure, just a namespace:
|
|
200
171
|
return uuidv5(`${uniqueNamespaceLikeDomainName}::${valueWithinNamespace}`, 'f9e1024d-21ac-473c-bac6-64796dd771dd');
|
|
201
172
|
}
|
|
202
|
-
|
|
203
173
|
function getInputUUID(a, b) {
|
|
204
174
|
let pluginId = a;
|
|
205
175
|
let remoteInputId = b;
|
|
@@ -207,7 +177,6 @@ function getInputUUID(a, b) {
|
|
|
207
177
|
pluginId = a.pluginId;
|
|
208
178
|
remoteInputId = a.remoteInputId;
|
|
209
179
|
}
|
|
210
|
-
|
|
211
180
|
if (!pluginId) throw new Error('getInputUUID: Cowardly rejecting a blank plugin_id');
|
|
212
181
|
if (!uuidIsValid(pluginId)) throw new Error(`Invalid pluginId:${pluginId}, should be a UUID`);
|
|
213
182
|
const rid = (remoteInputId || '').trim();
|
|
@@ -216,7 +185,6 @@ function getInputUUID(a, b) {
|
|
|
216
185
|
// 3d0e5d99-6ba9-4fab-9bb2-c32304d3df8e
|
|
217
186
|
return uuidv5(`${pluginId}:${rid}`, '3d0e5d99-6ba9-4fab-9bb2-c32304d3df8e');
|
|
218
187
|
}
|
|
219
|
-
|
|
220
188
|
const timestampMatch = /^\d{13}$/;
|
|
221
189
|
function dateFromString(s) {
|
|
222
190
|
if (typeof s === 'number') return new Date(s);
|
|
@@ -225,7 +193,6 @@ function dateFromString(s) {
|
|
|
225
193
|
}
|
|
226
194
|
return new Date(s);
|
|
227
195
|
}
|
|
228
|
-
|
|
229
196
|
function getUUIDv7(date, inputUuid) {
|
|
230
197
|
/* optional date and input UUID */
|
|
231
198
|
const uuid = inputUuid || uuidv7();
|
|
@@ -234,7 +201,6 @@ function getUUIDv7(date, inputUuid) {
|
|
|
234
201
|
const d = dateFromString(date);
|
|
235
202
|
// isNaN behaves differently than Number.isNaN -- we're actually going for the
|
|
236
203
|
// attempted conversion here
|
|
237
|
-
|
|
238
204
|
if (isNaN(d)) throw new Error(`getUUIDv7 got an invalid date:${date || '<blank>'}`);
|
|
239
205
|
const dateBytes = intToByteArray(d.getTime()).reverse();
|
|
240
206
|
dateBytes.slice(2, 8).forEach((b, i) => {
|
|
@@ -248,7 +214,6 @@ function getUUIDTimestamp(uuid) {
|
|
|
248
214
|
const ts = parseInt(`${uuid}`.replace(/-/g, '').slice(0, 12), 16);
|
|
249
215
|
return new Date(ts);
|
|
250
216
|
}
|
|
251
|
-
|
|
252
217
|
function getEntryTypeId(o, { defaults = {} } = {}) {
|
|
253
218
|
let id = o.entry_type_id || defaults.entry_type_id;
|
|
254
219
|
if (id) return id;
|
|
@@ -263,32 +228,27 @@ function getEntryTypeId(o, { defaults = {} } = {}) {
|
|
|
263
228
|
function getEntryType(o, defaults = {}) {
|
|
264
229
|
let etype = o.entry_type || defaults.entry_type;
|
|
265
230
|
if (etype) return etype;
|
|
266
|
-
|
|
267
231
|
const id = o.entry_type_id || defaults.entry_type_id;
|
|
268
|
-
|
|
269
232
|
etype = TIMELINE_ENTRY_TYPES[id];
|
|
270
233
|
if (etype === undefined) throw new Error(`Invalid entry_type: ${etype}`);
|
|
271
234
|
return etype;
|
|
272
235
|
}
|
|
273
|
-
|
|
274
236
|
const requiredTimelineEntryFields = ['ts', 'entry_type_id', 'input_id', 'person_id'];
|
|
275
|
-
|
|
276
237
|
function getTimelineEntryUUID(inputObject, { defaults = {} } = {}) {
|
|
277
238
|
const o = { ...defaults, ...inputObject };
|
|
278
239
|
/*
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
240
|
+
Outside systems CAN specify a unique UUID as remote_entry_uuid,
|
|
241
|
+
which will be used for updates, etc.
|
|
242
|
+
If not, it will be generated using whatever info we have
|
|
243
|
+
*/
|
|
283
244
|
if (o.remote_entry_uuid) {
|
|
284
245
|
if (!uuidIsValid(o.remote_entry_uuid)) throw new Error('Invalid remote_entry_uuid, it must be a UUID');
|
|
285
246
|
return o.remote_entry_uuid;
|
|
286
247
|
}
|
|
287
248
|
/*
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
249
|
+
Outside systems CAN specify a unique remote_entry_id
|
|
250
|
+
If not, it will be generated using whatever info we have
|
|
251
|
+
*/
|
|
292
252
|
if (o.remote_entry_id) {
|
|
293
253
|
// get a temp ID
|
|
294
254
|
if (!o.input_id)
|
|
@@ -300,17 +260,13 @@ function getTimelineEntryUUID(inputObject, { defaults = {} } = {}) {
|
|
|
300
260
|
return getUUIDv7(o.ts, uuid);
|
|
301
261
|
}
|
|
302
262
|
o.entry_type_id = getEntryTypeId(o);
|
|
303
|
-
|
|
304
263
|
const missing = requiredTimelineEntryFields.filter((d) => o[d] === undefined); // 0 could be an entry type value
|
|
305
|
-
|
|
306
264
|
if (missing.length > 0) throw new Error(`Missing required fields to append an entry_id:${missing.join(',')}`);
|
|
307
265
|
const ts = new Date(o.ts);
|
|
308
266
|
// isNaN behaves differently than Number.isNaN -- we're actually going for the
|
|
309
267
|
// attempted conversion here
|
|
310
|
-
|
|
311
268
|
if (isNaN(ts)) throw new Error(`getTimelineEntryUUID got an invalid date:${o.ts || '<blank>'}`);
|
|
312
269
|
const idString = `${ts.toISOString()}-${o.person_id}-${o.entry_type_id}-${o.source_code_id || 0}`;
|
|
313
|
-
|
|
314
270
|
if (!uuidIsValid(o.input_id)) {
|
|
315
271
|
throw new Error(`Invalid input_id:'${o.input_id}', type ${typeof o.input_id} -- should be a uuid`);
|
|
316
272
|
}
|
|
@@ -321,13 +277,11 @@ function getTimelineEntryUUID(inputObject, { defaults = {} } = {}) {
|
|
|
321
277
|
// may not match this standard, uuid sorting isn't guaranteed
|
|
322
278
|
return getUUIDv7(ts, uuid);
|
|
323
279
|
}
|
|
324
|
-
|
|
325
280
|
function getDateRangeArray(startDate, endDate) {
|
|
326
281
|
const start = new Date(startDate);
|
|
327
282
|
const end = new Date(endDate);
|
|
328
283
|
const result = [];
|
|
329
284
|
const msInDay = 24 * 60 * 60 * 1000;
|
|
330
|
-
|
|
331
285
|
function addDays(date, days) {
|
|
332
286
|
const d = new Date(date);
|
|
333
287
|
d.setDate(d.getDate() + days);
|
|
@@ -343,13 +297,10 @@ function getDateRangeArray(startDate, endDate) {
|
|
|
343
297
|
d.setFullYear(d.getFullYear() + years);
|
|
344
298
|
return d;
|
|
345
299
|
}
|
|
346
|
-
|
|
347
300
|
const diffDays = Math.floor((end - start) / msInDay);
|
|
348
301
|
const diffMonths = (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth());
|
|
349
302
|
const diffYears = end.getFullYear() - start.getFullYear();
|
|
350
|
-
|
|
351
303
|
let current = new Date(start);
|
|
352
|
-
|
|
353
304
|
let stepFn;
|
|
354
305
|
if (diffDays < 10) {
|
|
355
306
|
stepFn = (date) => addDays(date, 1);
|
|
@@ -364,7 +315,6 @@ function getDateRangeArray(startDate, endDate) {
|
|
|
364
315
|
} else {
|
|
365
316
|
stepFn = (date) => addYears(date, 1);
|
|
366
317
|
}
|
|
367
|
-
|
|
368
318
|
while (current <= end) {
|
|
369
319
|
result.push(new Date(current));
|
|
370
320
|
const next = stepFn(current);
|
|
@@ -377,7 +327,6 @@ function getDateRangeArray(startDate, endDate) {
|
|
|
377
327
|
}
|
|
378
328
|
return result;
|
|
379
329
|
}
|
|
380
|
-
|
|
381
330
|
class ObjectError extends Error {
|
|
382
331
|
constructor(data) {
|
|
383
332
|
if (typeof data === 'string') {
|
|
@@ -394,8 +343,44 @@ class ObjectError extends Error {
|
|
|
394
343
|
}
|
|
395
344
|
}
|
|
396
345
|
}
|
|
397
|
-
|
|
398
|
-
|
|
346
|
+
export { appendPostfix };
|
|
347
|
+
export { bool };
|
|
348
|
+
export { create };
|
|
349
|
+
export { list };
|
|
350
|
+
export { downloadFile };
|
|
351
|
+
export { extract };
|
|
352
|
+
export { ForEachEntry };
|
|
353
|
+
export { FileUtilities };
|
|
354
|
+
export { getBatchTransform };
|
|
355
|
+
export { getDateRangeArray };
|
|
356
|
+
export { getDebatchTransform };
|
|
357
|
+
export { getEntryType };
|
|
358
|
+
export { getEntryTypeId };
|
|
359
|
+
export { getFile };
|
|
360
|
+
export { getManifest };
|
|
361
|
+
export { getStringArray };
|
|
362
|
+
export { getTempDir };
|
|
363
|
+
export { getTempFilename };
|
|
364
|
+
export { getTimelineEntryUUID };
|
|
365
|
+
export { getPacketFiles };
|
|
366
|
+
export { getPluginUUID };
|
|
367
|
+
export { getInputUUID };
|
|
368
|
+
export { getUUIDv7 };
|
|
369
|
+
export { getUUIDTimestamp };
|
|
370
|
+
export { handlebars };
|
|
371
|
+
export { isValidDate };
|
|
372
|
+
export { makeStrings };
|
|
373
|
+
export { ObjectError };
|
|
374
|
+
export { parseJSON5 };
|
|
375
|
+
export { relativeDate };
|
|
376
|
+
export { streamPacket };
|
|
377
|
+
export { TIMELINE_ENTRY_TYPES };
|
|
378
|
+
export { writeTempFile };
|
|
379
|
+
export { uuidIsValid };
|
|
380
|
+
export { uuidv4 };
|
|
381
|
+
export { uuidv5 };
|
|
382
|
+
export { uuidv7 };
|
|
383
|
+
export default {
|
|
399
384
|
appendPostfix,
|
|
400
385
|
bool,
|
|
401
386
|
create,
|
package/package.json
CHANGED
package/test/cli.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import yargs from 'yargs/yargs';
|
|
2
|
+
import methods from '../index.js';
|
|
3
|
+
const argv = yargs(process.argv.slice(2)).parse();
|
|
4
4
|
async function run() {
|
|
5
5
|
if (typeof methods[argv._[0]] !== 'function') throw new Error(`${argv._[0]} is not a function`);
|
|
6
6
|
const output = await methods[argv._[0]](argv);
|
|
7
|
-
// eslint-disable-next-line no-console
|
|
8
7
|
console.log(output);
|
|
9
8
|
}
|
|
10
9
|
run();
|
package/test/file.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
|
|
1
|
+
import nodetest from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import * as debug$0 from 'debug';
|
|
4
|
+
import { FileUtilities } from '../index.js';
|
|
5
|
+
const { it } = nodetest;
|
|
6
|
+
const debug = debug$0('files');
|
|
7
7
|
it('Should list a directory', async () => {
|
|
8
8
|
const futil = new FileUtilities({ accountId: 'test' });
|
|
9
9
|
let files = await futil.list({ directory: '.' });
|
|
@@ -14,7 +14,6 @@ it('Should list a directory', async () => {
|
|
|
14
14
|
let endTest = await futil.list({ directory: '.', end: '1900-01-01' });
|
|
15
15
|
assert(endTest.length === 0, 'Should NOT have any files before past end date');
|
|
16
16
|
});
|
|
17
|
-
|
|
18
17
|
it('Should be able to analyze CSV files with and without header lines', async () => {
|
|
19
18
|
const futil = new FileUtilities({ accountId: 'test' });
|
|
20
19
|
const f1 = await futil.columns({ filename: __dirname + '/sample/fileWithHead.csv' });
|