@engine9-io/input-tools 1.9.11 → 2.0.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.
- package/ForEachEntry.js +18 -43
- package/ValidatingReadable.js +3 -6
- package/buildSamplePackets.js +11 -16
- package/eslint.config.mjs +15 -11
- package/file/FileUtilities.js +976 -1048
- 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 +334 -326
- package/index.js +60 -75
- 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,33 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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$0 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 uuid from "uuid";
|
|
3
12
|
const fsp = fs.promises;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const { PassThrough } = require('node:stream');
|
|
12
|
-
const progress = require('debug')('info:@engine9/input-tools');
|
|
13
|
-
const unzipper = require('unzipper');
|
|
14
|
-
|
|
15
|
-
const dayjs = require('dayjs');
|
|
16
|
-
|
|
17
|
-
const { S3Client, HeadObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
|
|
18
|
-
|
|
19
|
-
const { v7: uuidv7 } = require('uuid');
|
|
20
|
-
|
|
13
|
+
const debug = debug$0('@engine9/input-tools');
|
|
14
|
+
const { mkdirp } = mkdirp$0;
|
|
15
|
+
const { Transform } = nodestream;
|
|
16
|
+
const { PassThrough } = nodestream;
|
|
17
|
+
const progress = debug$0('info:@engine9/input-tools');
|
|
18
|
+
const { S3Client, HeadObjectCommand, GetObjectCommand } = clientS3;
|
|
19
|
+
const { v7: uuidv7 } = uuid;
|
|
21
20
|
async function getTempDir({ accountId = 'engine9' }) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
const dir = [os.tmpdir(), accountId, new Date().toISOString().substring(0, 10)].join(path.sep);
|
|
22
|
+
try {
|
|
23
|
+
await mkdirp(dir);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err.code !== 'EEXIST')
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
return dir;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
31
|
/*
|
|
32
32
|
Get a new, timestamp based filename, creating any necessary directories
|
|
33
33
|
options:
|
|
@@ -35,346 +35,354 @@ async function getTempDir({ accountId = 'engine9' }) {
|
|
|
35
35
|
source:source file, used to generate friendly name
|
|
36
36
|
*/
|
|
37
37
|
async function getTempFilename(options) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
let dir = await getTempDir(options);
|
|
39
|
+
const target = options.targetFilename;
|
|
40
|
+
if (target) {
|
|
41
|
+
if (target.indexOf('/') === 0 || target.indexOf('\\') === 0) {
|
|
42
|
+
// assume a full directory path has been specified
|
|
43
|
+
return target;
|
|
44
|
+
}
|
|
45
|
+
// make a distinct directory, so we don't overwrite the file
|
|
46
|
+
dir = `${dir}/${new Date()
|
|
47
|
+
.toISOString()
|
|
48
|
+
.slice(0, -6)
|
|
49
|
+
.replace(/[^0-9]/g, '_')}`;
|
|
50
|
+
const newDir = await mkdirp(dir);
|
|
51
|
+
return `${newDir}/${target}`;
|
|
52
|
+
}
|
|
53
|
+
let { prefix } = options;
|
|
54
|
+
let { postfix } = options;
|
|
55
|
+
const { targetFormat } = options;
|
|
56
|
+
if (!postfix && targetFormat === 'csv')
|
|
57
|
+
postfix = '.csv';
|
|
58
|
+
if (options.source) {
|
|
59
|
+
postfix = `_${options.source.split('/').pop()}`;
|
|
60
|
+
postfix = postfix.replace(/['"\\]/g, '').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
45
61
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.slice(0, -6)
|
|
51
|
-
.replace(/[^0-9]/g, '_')}`;
|
|
52
|
-
|
|
53
|
-
const newDir = await mkdirp(dir);
|
|
54
|
-
|
|
55
|
-
return `${newDir}/${target}`;
|
|
56
|
-
}
|
|
57
|
-
let { prefix } = options;
|
|
58
|
-
let { postfix } = options;
|
|
59
|
-
const { targetFormat } = options;
|
|
60
|
-
if (!postfix && targetFormat === 'csv') postfix = '.csv';
|
|
61
|
-
if (options.source) {
|
|
62
|
-
postfix = `_${options.source.split('/').pop()}`;
|
|
63
|
-
postfix = postfix.replace(/['"\\]/g, '').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (prefix) prefix += '_';
|
|
67
|
-
|
|
68
|
-
const p = `${dir}/${prefix || ''}${uuidv7()}${postfix || '.txt'}`;
|
|
69
|
-
return p;
|
|
62
|
+
if (prefix)
|
|
63
|
+
prefix += '_';
|
|
64
|
+
const p = `${dir}/${prefix || ''}${uuidv7()}${postfix || '.txt'}`;
|
|
65
|
+
return p;
|
|
70
66
|
}
|
|
71
|
-
|
|
72
67
|
async function writeTempFile(options) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return { filename };
|
|
68
|
+
const { content, postfix = '.txt' } = options;
|
|
69
|
+
const filename = await getTempFilename({ ...options, postfix });
|
|
70
|
+
await fsp.writeFile(filename, content);
|
|
71
|
+
return { filename };
|
|
78
72
|
}
|
|
79
|
-
|
|
80
73
|
async function getPacketFiles({ packet }) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
return ptStream;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
74
|
+
if (packet.indexOf('s3://') === 0) {
|
|
75
|
+
const parts = packet.split('/');
|
|
76
|
+
const Bucket = parts[2];
|
|
77
|
+
const Key = parts.slice(3).join('/');
|
|
78
|
+
const s3Client = new S3Client({});
|
|
79
|
+
debug('Getting ', { Bucket, Key });
|
|
80
|
+
// const directory = await unzipper.Open.s3(s3Client, { Bucket, Key });
|
|
81
|
+
let size = null;
|
|
82
|
+
const directory = await unzipper.Open.custom({
|
|
83
|
+
async size() {
|
|
84
|
+
const info = await s3Client.send(new HeadObjectCommand({
|
|
85
|
+
Bucket,
|
|
86
|
+
Key
|
|
87
|
+
}));
|
|
88
|
+
size = info.ContentLength;
|
|
89
|
+
progress(`Retrieving file of size ${size / (1024 * 1024)} MB`);
|
|
90
|
+
return info.ContentLength;
|
|
91
|
+
},
|
|
92
|
+
stream(offset, length) {
|
|
93
|
+
const ptStream = new PassThrough();
|
|
94
|
+
s3Client
|
|
95
|
+
.send(new GetObjectCommand({
|
|
96
|
+
Bucket,
|
|
97
|
+
Key,
|
|
98
|
+
Range: `bytes=${offset}-${length ?? ''}`
|
|
99
|
+
}))
|
|
100
|
+
.then((response) => {
|
|
101
|
+
response.Body.pipe(ptStream);
|
|
102
|
+
})
|
|
103
|
+
.catch((error) => {
|
|
104
|
+
ptStream.emit('error', error);
|
|
105
|
+
});
|
|
106
|
+
return ptStream;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return directory;
|
|
110
|
+
}
|
|
111
|
+
const directory = await unzipper.Open.file(packet);
|
|
125
112
|
return directory;
|
|
126
|
-
}
|
|
127
|
-
const directory = await unzipper.Open.file(packet);
|
|
128
|
-
return directory;
|
|
129
113
|
}
|
|
130
|
-
|
|
131
114
|
async function getManifest({ packet }) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
115
|
+
if (!packet)
|
|
116
|
+
throw new Error('no packet option specififed');
|
|
117
|
+
const { files } = await getPacketFiles({ packet });
|
|
118
|
+
const file = files.find((d) => d.path === 'manifest.json');
|
|
119
|
+
const content = await file.buffer();
|
|
120
|
+
const manifest = JSON.parse(content.toString());
|
|
121
|
+
return manifest;
|
|
138
122
|
}
|
|
139
|
-
|
|
140
123
|
function getBatchTransform({ batchSize = 100 }) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
124
|
+
return {
|
|
125
|
+
transform: new Transform({
|
|
126
|
+
objectMode: true,
|
|
127
|
+
transform(chunk, encoding, cb) {
|
|
128
|
+
this.buffer = (this.buffer || []).concat(chunk);
|
|
129
|
+
if (this.buffer.length >= batchSize) {
|
|
130
|
+
this.push(this.buffer);
|
|
131
|
+
this.buffer = [];
|
|
132
|
+
}
|
|
133
|
+
cb();
|
|
134
|
+
},
|
|
135
|
+
flush(cb) {
|
|
136
|
+
if (this.buffer?.length > 0)
|
|
137
|
+
this.push(this.buffer);
|
|
138
|
+
cb();
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
};
|
|
158
142
|
}
|
|
159
143
|
function getDebatchTransform() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
144
|
+
return {
|
|
145
|
+
transform: new Transform({
|
|
146
|
+
objectMode: true,
|
|
147
|
+
transform(chunk, encoding, cb) {
|
|
148
|
+
chunk.forEach((c) => this.push(c));
|
|
149
|
+
cb();
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
};
|
|
169
153
|
}
|
|
170
|
-
|
|
171
154
|
async function getFile({ filename, packet, type }) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
155
|
+
if (!packet && !filename)
|
|
156
|
+
throw new Error('no packet option specififed');
|
|
157
|
+
let content = null;
|
|
158
|
+
let filePath = null;
|
|
159
|
+
if (packet) {
|
|
160
|
+
const manifest = await getManifest({ packet });
|
|
161
|
+
const manifestFiles = manifest.files?.filter((d) => d.type === type);
|
|
162
|
+
if (!manifestFiles?.length)
|
|
163
|
+
throw new Error(`No files of type ${type} found in packet`);
|
|
164
|
+
if (manifestFiles?.length > 1)
|
|
165
|
+
throw new Error(`Multiple files of type ${type} found in packet`);
|
|
166
|
+
filePath = manifestFiles[0].path;
|
|
167
|
+
const { files } = await getPacketFiles({ packet });
|
|
168
|
+
const handle = files.find((d) => d.path === filePath);
|
|
169
|
+
const buffer = await handle.buffer();
|
|
170
|
+
content = await buffer.toString();
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
content = await fsp.readFile(filename);
|
|
174
|
+
filePath = filename.split('/').pop();
|
|
175
|
+
}
|
|
176
|
+
if (filePath.slice(-5) === '.json' || filePath.slice(-6) === '.json5') {
|
|
177
|
+
try {
|
|
178
|
+
return JSON5.parse(content);
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
debug(`Erroring parsing json content from ${path}`, content);
|
|
182
|
+
throw e;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return content;
|
|
186
|
+
}
|
|
187
|
+
async function streamPacket({ packet, type }) {
|
|
188
|
+
if (!packet)
|
|
189
|
+
throw new Error('no packet option specififed');
|
|
176
190
|
const manifest = await getManifest({ packet });
|
|
177
191
|
const manifestFiles = manifest.files?.filter((d) => d.type === type);
|
|
178
|
-
if (!manifestFiles?.length)
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
if (!manifestFiles?.length)
|
|
193
|
+
throw new Error(`No files of type ${type} found in packet`);
|
|
194
|
+
if (manifestFiles?.length > 1)
|
|
195
|
+
throw new Error(`Multiple files of type ${type} found in packet`);
|
|
196
|
+
const filePath = manifestFiles[0].path;
|
|
181
197
|
const { files } = await getPacketFiles({ packet });
|
|
182
198
|
const handle = files.find((d) => d.path === filePath);
|
|
183
|
-
|
|
184
|
-
content = await buffer.toString();
|
|
185
|
-
} else {
|
|
186
|
-
content = await fsp.readFile(filename);
|
|
187
|
-
filePath = filename.split('/').pop();
|
|
188
|
-
}
|
|
189
|
-
if (filePath.slice(-5) === '.json' || filePath.slice(-6) === '.json5') {
|
|
190
|
-
try {
|
|
191
|
-
return JSON5.parse(content);
|
|
192
|
-
} catch (e) {
|
|
193
|
-
debug(`Erroring parsing json content from ${path}`, content);
|
|
194
|
-
throw e;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return content;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async function streamPacket({ packet, type }) {
|
|
201
|
-
if (!packet) throw new Error('no packet option specififed');
|
|
202
|
-
const manifest = await getManifest({ packet });
|
|
203
|
-
const manifestFiles = manifest.files?.filter((d) => d.type === type);
|
|
204
|
-
if (!manifestFiles?.length) throw new Error(`No files of type ${type} found in packet`);
|
|
205
|
-
if (manifestFiles?.length > 1) throw new Error(`Multiple files of type ${type} found in packet`);
|
|
206
|
-
const filePath = manifestFiles[0].path;
|
|
207
|
-
const { files } = await getPacketFiles({ packet });
|
|
208
|
-
const handle = files.find((d) => d.path === filePath);
|
|
209
|
-
return { stream: handle.stream(), path: filePath };
|
|
199
|
+
return { stream: handle.stream(), path: filePath };
|
|
210
200
|
}
|
|
211
|
-
|
|
212
201
|
async function downloadFile({ packet, type = 'person' }) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
202
|
+
const { stream: fileStream, path: filePath } = await streamPacket({ packet, type });
|
|
203
|
+
const filename = await getTempFilename({ targetFilename: filePath.split('/').pop() });
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
fileStream
|
|
206
|
+
.pipe(fs.createWriteStream(filename))
|
|
207
|
+
.on('error', reject)
|
|
208
|
+
.on('finish', () => {
|
|
209
|
+
resolve({ filename });
|
|
210
|
+
});
|
|
211
|
+
});
|
|
224
212
|
}
|
|
225
|
-
|
|
226
213
|
function isValidDate(d) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return d instanceof Date && !isNaN(d);
|
|
214
|
+
// we WANT to use isNaN, not the Number.isNaN -- we're checking the date type
|
|
215
|
+
return d instanceof Date && !isNaN(d);
|
|
230
216
|
}
|
|
231
|
-
|
|
232
217
|
function bool(x, _defaultVal) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
218
|
+
const defaultVal = _defaultVal === undefined ? false : _defaultVal;
|
|
219
|
+
if (x === undefined || x === null || x === '')
|
|
220
|
+
return defaultVal;
|
|
221
|
+
if (typeof x !== 'string')
|
|
222
|
+
return !!x;
|
|
223
|
+
if (x === '1')
|
|
224
|
+
return true; // 0 will return false, but '1' is true
|
|
225
|
+
const y = x.toLowerCase();
|
|
226
|
+
return !!(y.indexOf('y') + 1) || !!(y.indexOf('t') + 1);
|
|
239
227
|
}
|
|
240
228
|
function getStringArray(s, nonZeroLength) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
229
|
+
let a = s || [];
|
|
230
|
+
if (typeof a === 'number')
|
|
231
|
+
a = String(a);
|
|
232
|
+
if (typeof a === 'string')
|
|
233
|
+
a = [a];
|
|
234
|
+
if (typeof s === 'string')
|
|
235
|
+
a = s.split(',');
|
|
236
|
+
a = a.map((x) => x.toString().trim()).filter(Boolean);
|
|
237
|
+
if (nonZeroLength && a.length === 0)
|
|
238
|
+
a = [0];
|
|
239
|
+
return a;
|
|
249
240
|
}
|
|
250
241
|
function relativeDate(s, _initialDate) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
let r = s.match(/^([+-]{1})([0-9]+)([YyMwdhms]{1})([.a-z]*)$/);
|
|
269
|
-
|
|
270
|
-
if (r) {
|
|
271
|
-
let period = null;
|
|
272
|
-
switch (r[3]) {
|
|
273
|
-
case 'Y':
|
|
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;
|
|
242
|
+
let initialDate = _initialDate;
|
|
243
|
+
if (!s || s === 'none')
|
|
244
|
+
return null;
|
|
245
|
+
if (typeof s.getMonth === 'function')
|
|
246
|
+
return s;
|
|
247
|
+
// We actually want a double equals here to test strings as well
|
|
248
|
+
if (parseInt(s, 10) == s) {
|
|
249
|
+
const r = new Date(parseInt(s, 10));
|
|
250
|
+
if (!isValidDate(r))
|
|
251
|
+
throw new Error(`Invalid integer date:${s}`);
|
|
252
|
+
return r;
|
|
253
|
+
}
|
|
254
|
+
if (initialDate) {
|
|
255
|
+
initialDate = new Date(initialDate);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
initialDate = new Date();
|
|
299
259
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
260
|
+
let r = s.match(/^([+-]{1})([0-9]+)([YyMwdhms]{1})([.a-z]*)$/);
|
|
261
|
+
if (r) {
|
|
262
|
+
let period = null;
|
|
263
|
+
switch (r[3]) {
|
|
264
|
+
case 'Y':
|
|
265
|
+
case 'y':
|
|
266
|
+
period = 'years';
|
|
267
|
+
break;
|
|
268
|
+
case 'M':
|
|
269
|
+
period = 'months';
|
|
270
|
+
break;
|
|
271
|
+
case 'w':
|
|
272
|
+
period = 'weeks';
|
|
273
|
+
break;
|
|
274
|
+
case 'd':
|
|
275
|
+
period = 'days';
|
|
276
|
+
break;
|
|
277
|
+
case 'h':
|
|
278
|
+
period = 'hours';
|
|
279
|
+
break;
|
|
280
|
+
case 'm':
|
|
281
|
+
period = 'minutes';
|
|
282
|
+
break;
|
|
283
|
+
case 's':
|
|
284
|
+
period = 'seconds';
|
|
285
|
+
break;
|
|
286
|
+
default:
|
|
287
|
+
period = 'minutes';
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
let d = dayjs(initialDate);
|
|
291
|
+
if (r[1] === '+') {
|
|
292
|
+
d = d.add(parseInt(r[2], 10), period);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
d = d.subtract(parseInt(r[2], 10), period);
|
|
296
|
+
}
|
|
297
|
+
if (!isValidDate(d.toDate()))
|
|
298
|
+
throw new Error(`Invalid date configuration:${r}`);
|
|
299
|
+
if (r[4]) {
|
|
300
|
+
const opts = r[4].split('.').filter(Boolean);
|
|
301
|
+
if (opts[0] === 'start')
|
|
302
|
+
d = d.startOf(opts[1] || 'day');
|
|
303
|
+
else if (opts[0] === 'end')
|
|
304
|
+
d = d.endOf(opts[1] || 'day');
|
|
305
|
+
else
|
|
306
|
+
throw new Error(`Invalid relative date,unknown options:${r[4]}`);
|
|
307
|
+
}
|
|
308
|
+
return d.toDate();
|
|
307
309
|
}
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (opts[0] === 'start') d = d.startOf(opts[1] || 'day');
|
|
312
|
-
else if (opts[0] === 'end') d = d.endOf(opts[1] || 'day');
|
|
313
|
-
else throw new Error(`Invalid relative date,unknown options:${r[4]}`);
|
|
310
|
+
if (s === 'now') {
|
|
311
|
+
r = dayjs(new Date()).toDate();
|
|
312
|
+
return r;
|
|
314
313
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (s === 'now') {
|
|
319
|
-
r = dayjs(new Date()).toDate();
|
|
314
|
+
r = dayjs(new Date(s)).toDate();
|
|
315
|
+
if (!isValidDate(r))
|
|
316
|
+
throw new Error(`Invalid Date: ${s}`);
|
|
320
317
|
return r;
|
|
321
|
-
}
|
|
322
|
-
r = dayjs(new Date(s)).toDate();
|
|
323
|
-
if (!isValidDate(r)) throw new Error(`Invalid Date: ${s}`);
|
|
324
|
-
return r;
|
|
325
318
|
}
|
|
326
|
-
|
|
327
319
|
/*
|
|
328
320
|
When comparing two objects, some may come from a file (thus strings), and some from
|
|
329
321
|
a database or elsewhere (not strings), so for deduping make sure to make them all strings
|
|
330
322
|
*/
|
|
331
323
|
function makeStrings(o) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
324
|
+
return Object.entries(o).reduce((a, [k, v]) => {
|
|
325
|
+
a[k] = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
326
|
+
return a;
|
|
327
|
+
}, {});
|
|
336
328
|
}
|
|
337
329
|
function appendPostfix(filename, postfix) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
330
|
+
const filenameParts = filename.split('/');
|
|
331
|
+
const fileParts = filenameParts
|
|
332
|
+
.slice(-1)[0]
|
|
333
|
+
.split('.')
|
|
334
|
+
.filter(Boolean)
|
|
335
|
+
.filter((d) => d !== postfix);
|
|
336
|
+
let targetFile = null;
|
|
337
|
+
if (fileParts.slice(-1)[0] === 'gz') {
|
|
338
|
+
targetFile = fileParts.slice(0, -2).concat(postfix).concat(fileParts.slice(-2)).join('.');
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
targetFile = fileParts.slice(0, -1).concat(postfix).concat(fileParts.slice(-1)).join('.');
|
|
342
|
+
}
|
|
343
|
+
return filenameParts.slice(0, -1).concat(targetFile).join('/');
|
|
352
344
|
}
|
|
353
|
-
|
|
354
345
|
function parseJSON5(o, defaultVal) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
346
|
+
if (o) {
|
|
347
|
+
if (typeof o === 'object')
|
|
348
|
+
return o;
|
|
349
|
+
if (typeof o === 'string')
|
|
350
|
+
return JSON5.parse(o);
|
|
351
|
+
throw new Error(`Could not parse object:${o}`);
|
|
352
|
+
}
|
|
353
|
+
return defaultVal || o;
|
|
361
354
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
355
|
+
export { appendPostfix };
|
|
356
|
+
export { bool };
|
|
357
|
+
export { downloadFile };
|
|
358
|
+
export { getTempFilename };
|
|
359
|
+
export { getTempDir };
|
|
360
|
+
export { getBatchTransform };
|
|
361
|
+
export { getDebatchTransform };
|
|
362
|
+
export { getFile };
|
|
363
|
+
export { getManifest };
|
|
364
|
+
export { getPacketFiles };
|
|
365
|
+
export { getStringArray };
|
|
366
|
+
export { makeStrings };
|
|
367
|
+
export { parseJSON5 };
|
|
368
|
+
export { relativeDate };
|
|
369
|
+
export { streamPacket };
|
|
370
|
+
export { writeTempFile };
|
|
371
|
+
export default {
|
|
372
|
+
appendPostfix,
|
|
373
|
+
bool,
|
|
374
|
+
downloadFile,
|
|
375
|
+
getTempFilename,
|
|
376
|
+
getTempDir,
|
|
377
|
+
getBatchTransform,
|
|
378
|
+
getDebatchTransform,
|
|
379
|
+
getFile,
|
|
380
|
+
getManifest,
|
|
381
|
+
getPacketFiles,
|
|
382
|
+
getStringArray,
|
|
383
|
+
makeStrings,
|
|
384
|
+
parseJSON5,
|
|
385
|
+
relativeDate,
|
|
386
|
+
streamPacket,
|
|
387
|
+
writeTempFile
|
|
380
388
|
};
|