make-mp-data 1.5.54 → 1.5.56
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/.vscode/launch.json +20 -4
- package/components/ai.js +28 -14
- package/components/utils.js +29 -9
- package/index.js +159 -29
- package/log.json +1678 -0
- package/package.json +14 -5
- package/scratch.mjs +4 -2
- package/scripts/deploy.sh +5 -1
- package/types.d.ts +8 -7
- package/dungeons/adspend.js +0 -96
- package/dungeons/anon.js +0 -104
- package/dungeons/big.js +0 -233
- package/dungeons/business.js +0 -327
- package/dungeons/complex.js +0 -396
- package/dungeons/foobar.js +0 -241
- package/dungeons/funnels.js +0 -220
- package/dungeons/gaming-experiments.js +0 -323
- package/dungeons/gaming.js +0 -314
- package/dungeons/governance.js +0 -288
- package/dungeons/mirror.js +0 -129
- package/dungeons/sanity.js +0 -118
- package/dungeons/scd.js +0 -205
- package/dungeons/session-replay.js +0 -175
- package/dungeons/simple.js +0 -151
- package/dungeons/userAgent.js +0 -190
package/.vscode/launch.json
CHANGED
|
@@ -13,16 +13,31 @@
|
|
|
13
13
|
{
|
|
14
14
|
"type": "node",
|
|
15
15
|
"request": "launch",
|
|
16
|
-
"name": "run dungeon",
|
|
16
|
+
"name": "run dungeon (debug)",
|
|
17
17
|
"runtimeExecutable": "nodemon",
|
|
18
|
-
"runtimeArgs": ["--inspect"],
|
|
18
|
+
"runtimeArgs": ["--inspect", "--ignore", "./data"],
|
|
19
|
+
"program": "${workspaceFolder}/index.js",
|
|
20
|
+
"args": ["${file}"],
|
|
21
|
+
"restart": true,
|
|
22
|
+
"console": "integratedTerminal",
|
|
23
|
+
"internalConsoleOptions": "neverOpen",
|
|
24
|
+
"skipFiles": ["<node_internals>/**"],
|
|
25
|
+
"preLaunchTask": "npm: prune",
|
|
26
|
+
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "node",
|
|
30
|
+
"request": "launch",
|
|
31
|
+
"name": "run dungeon (one shot)",
|
|
32
|
+
"runtimeExecutable": "node",
|
|
19
33
|
"program": "${workspaceFolder}/index.js",
|
|
20
|
-
"args": ["
|
|
34
|
+
"args": ["${file}"],
|
|
21
35
|
"restart": true,
|
|
22
36
|
"console": "integratedTerminal",
|
|
23
37
|
"internalConsoleOptions": "neverOpen",
|
|
24
38
|
"skipFiles": ["<node_internals>/**"],
|
|
25
39
|
"preLaunchTask": "npm: prune",
|
|
40
|
+
|
|
26
41
|
},
|
|
27
42
|
{
|
|
28
43
|
"type": "node",
|
|
@@ -35,7 +50,8 @@
|
|
|
35
50
|
"internalConsoleOptions": "neverOpen",
|
|
36
51
|
"env": {
|
|
37
52
|
"NODE_ENV": "dev"
|
|
38
|
-
}
|
|
53
|
+
},
|
|
54
|
+
"runtimeArgs": ["--ignore", "./data"],
|
|
39
55
|
},
|
|
40
56
|
{
|
|
41
57
|
"command": "npm run func:local",
|
package/components/ai.js
CHANGED
|
@@ -83,21 +83,35 @@ function validator(schema) {
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
if (require.main === module) {
|
|
86
|
-
generateSchema(`
|
|
86
|
+
generateSchema(`https://apps.apple.com/us/app/call-guardian-for-us-cellular/id1228680023 call guardian is an app for blocking spam calls made by TNS
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
this is the list of events we want:
|
|
89
|
+
|
|
90
|
+
Onboarding Started
|
|
91
|
+
Onboarding Completed (Basic)
|
|
92
|
+
Onboarding Completed (Premium)
|
|
93
|
+
Page Views (all "screens" within the app")
|
|
94
|
+
Enable/Disable High Risk Blocking
|
|
95
|
+
Enable/Disable Medium Risk Blocking
|
|
96
|
+
Enable/Disable Neighborhood Spoof Blocking
|
|
97
|
+
Call Blocked (Spam)
|
|
98
|
+
Call Blocked (Custom List)
|
|
99
|
+
Branded Call w/o Logo Received
|
|
100
|
+
Branded Call w/ Logo Received
|
|
101
|
+
Branded Call Answered
|
|
102
|
+
Branded Call Blocked
|
|
103
|
+
Enable/Disable Text Spam
|
|
104
|
+
Reverse Number Lookup
|
|
105
|
+
Report as Spam
|
|
106
|
+
Report as Not Spam
|
|
107
|
+
Custom Block List Number Add
|
|
108
|
+
Custom Block List Number Remove
|
|
109
|
+
Call Arrives Before Push
|
|
110
|
+
Error Scenarios
|
|
111
|
+
User Can't Authenticate
|
|
112
|
+
Xfinity Services Can't Connect
|
|
113
|
+
Verizon Services Can't Connect
|
|
114
|
+
Deep Links into app`)
|
|
101
115
|
.then((result) => {
|
|
102
116
|
if (NODE_ENV === "dev") debugger;
|
|
103
117
|
})
|
package/components/utils.js
CHANGED
|
@@ -6,7 +6,7 @@ const { spawn } = require('child_process');
|
|
|
6
6
|
const dayjs = require('dayjs');
|
|
7
7
|
const utc = require('dayjs/plugin/utc');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const { mkdir } = require('ak-tools');
|
|
9
|
+
const { mkdir, parseGCSUri } = require('ak-tools');
|
|
10
10
|
const { existsSync } = require('fs');
|
|
11
11
|
dayjs.extend(utc);
|
|
12
12
|
require('dotenv').config();
|
|
@@ -26,6 +26,11 @@ let chanceInitialized = false;
|
|
|
26
26
|
const ACTUAL_NOW = dayjs.utc();
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
const { Storage: cloudStorage } = require('@google-cloud/storage');
|
|
30
|
+
const projectId = 'YOUR_PROJECT_ID';
|
|
31
|
+
const storage = new cloudStorage({ projectId });
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
/*
|
|
30
35
|
----
|
|
31
36
|
RNG
|
|
@@ -184,10 +189,10 @@ function choose(value) {
|
|
|
184
189
|
try {
|
|
185
190
|
// Keep resolving the value if it's a function
|
|
186
191
|
while (typeof value === 'function') {
|
|
187
|
-
value = value();
|
|
192
|
+
value = value();
|
|
188
193
|
}
|
|
189
194
|
|
|
190
|
-
|
|
195
|
+
|
|
191
196
|
// [[],[],[]] should pick one
|
|
192
197
|
if (Array.isArray(value) && Array.isArray(value[0])) {
|
|
193
198
|
return chance.pickone(value);
|
|
@@ -434,23 +439,38 @@ STREAMERS
|
|
|
434
439
|
----
|
|
435
440
|
*/
|
|
436
441
|
|
|
437
|
-
function streamJSON(
|
|
442
|
+
function streamJSON(filePath, data) {
|
|
438
443
|
return new Promise((resolve, reject) => {
|
|
439
|
-
|
|
444
|
+
let writeStream;
|
|
445
|
+
if (filePath?.startsWith('gs://')) {
|
|
446
|
+
const { uri, bucket, file } = parseGCSUri(filePath);
|
|
447
|
+
writeStream = storage.bucket(bucket).file(file).createWriteStream({ gzip: true });
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
writeStream = fs.createWriteStream(filePath, { encoding: 'utf8' });
|
|
451
|
+
}
|
|
440
452
|
data.forEach(item => {
|
|
441
453
|
writeStream.write(JSON.stringify(item) + '\n');
|
|
442
454
|
});
|
|
443
455
|
writeStream.end();
|
|
444
456
|
writeStream.on('finish', () => {
|
|
445
|
-
resolve(
|
|
457
|
+
resolve(filePath);
|
|
446
458
|
});
|
|
447
459
|
writeStream.on('error', reject);
|
|
448
460
|
});
|
|
449
461
|
}
|
|
450
462
|
|
|
451
|
-
function streamCSV(
|
|
463
|
+
function streamCSV(filePath, data) {
|
|
452
464
|
return new Promise((resolve, reject) => {
|
|
453
|
-
|
|
465
|
+
let writeStream;
|
|
466
|
+
if (filePath?.startsWith('gs://')) {
|
|
467
|
+
const { uri, bucket, file } = parseGCSUri(filePath);
|
|
468
|
+
writeStream = storage.bucket(bucket).file(file).createWriteStream({ gzip: true });
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
writeStream = fs.createWriteStream(filePath, { encoding: 'utf8' });
|
|
472
|
+
}
|
|
473
|
+
|
|
454
474
|
// Extract all unique keys from the data array
|
|
455
475
|
const columns = getUniqueKeys(data); // Assuming getUniqueKeys properly retrieves all keys
|
|
456
476
|
|
|
@@ -469,7 +489,7 @@ function streamCSV(path, data) {
|
|
|
469
489
|
|
|
470
490
|
writeStream.end();
|
|
471
491
|
writeStream.on('finish', () => {
|
|
472
|
-
resolve(
|
|
492
|
+
resolve(filePath);
|
|
473
493
|
});
|
|
474
494
|
writeStream.on('error', reject);
|
|
475
495
|
});
|
package/index.js
CHANGED
|
@@ -45,6 +45,22 @@ const t = require('ak-tools');
|
|
|
45
45
|
|
|
46
46
|
//CLOUD
|
|
47
47
|
const functions = require('@google-cloud/functions-framework');
|
|
48
|
+
const { GoogleAuth } = require('google-auth-library');
|
|
49
|
+
const CONCURRENCY = 1_000;
|
|
50
|
+
let RUNTIME_URL = "https://dm4-lmozz6xkha-uc.a.run.app"; // IMPORTANT: this is what allows the service to call itself
|
|
51
|
+
// const functionName = process.env.FUNCTION_NAME || process.env.K_SERVICE;
|
|
52
|
+
|
|
53
|
+
// const region = process.env.REGION; // Optionally, you can get the region too
|
|
54
|
+
// const GCP_PROJECT = process.env.GCLOUD_PROJECT; // Project ID is also available as an environment variable
|
|
55
|
+
// const isCloudFunction = !!process.env.FUNCTION_NAME || !!process.env.FUNCTION_TARGET;
|
|
56
|
+
// if (isCloudFunction) {
|
|
57
|
+
// RUNTIME_URL = `https://${region}-${GCP_PROJECT}.cloudfunctions.net/${functionName}`;
|
|
58
|
+
// }
|
|
59
|
+
// else {
|
|
60
|
+
// RUNTIME_URL = `http://localhost:8080`;
|
|
61
|
+
// }
|
|
62
|
+
|
|
63
|
+
|
|
48
64
|
|
|
49
65
|
// DEFAULTS
|
|
50
66
|
const { campaigns, devices, locations } = require('./components/defaults.js');
|
|
@@ -74,7 +90,6 @@ let eventCount = 0;
|
|
|
74
90
|
let userCount = 0;
|
|
75
91
|
|
|
76
92
|
|
|
77
|
-
|
|
78
93
|
/**
|
|
79
94
|
* generates fake mixpanel data
|
|
80
95
|
* @param {Config} config
|
|
@@ -331,65 +346,161 @@ async function main(config) {
|
|
|
331
346
|
jobTimer.stop(false);
|
|
332
347
|
const { start, end, delta, human } = jobTimer.report(false);
|
|
333
348
|
|
|
334
|
-
if (process.env.NODE_ENV === 'dev')
|
|
349
|
+
// if (process.env.NODE_ENV === 'dev')debugger;
|
|
335
350
|
return {
|
|
336
351
|
...STORAGE,
|
|
337
352
|
importResults,
|
|
338
353
|
files,
|
|
354
|
+
operations,
|
|
355
|
+
eventCount,
|
|
356
|
+
userCount,
|
|
339
357
|
time: { start, end, delta, human },
|
|
340
358
|
};
|
|
341
359
|
}
|
|
342
360
|
|
|
343
|
-
|
|
344
|
-
|
|
345
361
|
functions.http('entry', async (req, res) => {
|
|
346
362
|
const reqTimer = timer('request');
|
|
347
363
|
reqTimer.start();
|
|
348
364
|
let response = {};
|
|
349
365
|
let script = req.body || "";
|
|
350
|
-
|
|
366
|
+
const params = { replicate: 1, is_replica: "false", runId: "", seed: "", ...req.query };
|
|
367
|
+
const replicate = Number(params.replicate);
|
|
368
|
+
// @ts-ignore
|
|
369
|
+
if (params?.is_replica === "true") params.is_replica = true;
|
|
370
|
+
// @ts-ignore
|
|
371
|
+
else params.is_replica = false;
|
|
372
|
+
const isReplica = params.is_replica;
|
|
373
|
+
isBATCH_MODE = true;
|
|
374
|
+
if (!params.runId) params.runId = uid(42);
|
|
351
375
|
try {
|
|
352
|
-
sLog("DM4: start");
|
|
353
376
|
if (!script) throw new Error("no script");
|
|
354
377
|
|
|
355
378
|
// Replace require("../ with require("./
|
|
356
|
-
script = script.replace(/require\("\.\.\//g, 'require("./');
|
|
379
|
+
// script = script.replace(/require\("\.\.\//g, 'require("./');
|
|
357
380
|
// ^ need to replace this because of the way the script is passed in... this is sketch
|
|
358
381
|
|
|
359
382
|
/** @type {Config} */
|
|
360
383
|
const config = eval(script);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
384
|
+
if (isReplica) {
|
|
385
|
+
const newSeed = (Math.random() / Math.random() / Math.random() / Math.random() / Math.random() / Math.random()).toString();
|
|
386
|
+
config.seed = newSeed;
|
|
387
|
+
params.seed = newSeed;
|
|
388
|
+
}
|
|
365
389
|
|
|
366
390
|
/** @type {Config} */
|
|
367
391
|
const optionsYouCantChange = {
|
|
368
|
-
verbose: false
|
|
369
|
-
writeToDisk: false,
|
|
370
|
-
|
|
392
|
+
verbose: false
|
|
371
393
|
};
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
394
|
+
|
|
395
|
+
if (replicate <= 1 || isReplica) {
|
|
396
|
+
if (isReplica) sLog("DM4: worker start", params);
|
|
397
|
+
// @ts-ignore
|
|
398
|
+
const { files = [], operations = 0, eventCount = 0, userCount = 0 } = await main({
|
|
399
|
+
...config,
|
|
400
|
+
...optionsYouCantChange,
|
|
401
|
+
});
|
|
402
|
+
reqTimer.stop(false);
|
|
403
|
+
response = { files, operations, eventCount, userCount };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
else {
|
|
407
|
+
sLog(`DM4: job start (${replicate} workers)`, params);
|
|
408
|
+
const results = await spawn_file_workers(replicate, script, params);
|
|
409
|
+
response = results;
|
|
410
|
+
}
|
|
380
411
|
}
|
|
381
412
|
catch (e) {
|
|
382
|
-
sLog("DM4: error", { error: e.message });
|
|
413
|
+
sLog("DM4: error", { error: e.message, stack: e.stack }, "ERROR");
|
|
383
414
|
response = { error: e.message };
|
|
384
415
|
res.status(500);
|
|
385
|
-
await rm(writePath);
|
|
386
416
|
}
|
|
417
|
+
|
|
387
418
|
finally {
|
|
419
|
+
reqTimer.stop(false);
|
|
420
|
+
const { start, end, delta, human } = reqTimer.report(false);
|
|
421
|
+
if (!isReplica) {
|
|
422
|
+
sLog(`DM4: job end (${human})`, { human, delta, ...params, ...response });
|
|
423
|
+
}
|
|
424
|
+
if (isReplica) {
|
|
425
|
+
const eps = Math.floor(((response?.eventCount || 0) / delta) * 1000);
|
|
426
|
+
sLog(`DM4: worker end (${human})`, { human, delta, eps, ...params, ...response });
|
|
427
|
+
}
|
|
428
|
+
response = { ...response, start, end, delta, human, ...params };
|
|
388
429
|
res.send(response);
|
|
430
|
+
return;
|
|
389
431
|
}
|
|
390
432
|
});
|
|
391
433
|
|
|
392
434
|
|
|
435
|
+
/**
|
|
436
|
+
* @typedef {import('mixpanel-import').ImportResults} ImportResults
|
|
437
|
+
*/
|
|
438
|
+
async function spawn_file_workers(numberWorkers, payload, params) {
|
|
439
|
+
const auth = new GoogleAuth();
|
|
440
|
+
let client;
|
|
441
|
+
if (RUNTIME_URL.includes('localhost')) {
|
|
442
|
+
client = await auth.getClient();
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
client = await auth.getIdTokenClient(RUNTIME_URL);
|
|
446
|
+
}
|
|
447
|
+
const limit = pLimit(CONCURRENCY);
|
|
448
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
449
|
+
|
|
450
|
+
const requestPromises = Array.from({ length: numberWorkers }, async (_, index) => {
|
|
451
|
+
index = index + 1;
|
|
452
|
+
await delay(index * 108);
|
|
453
|
+
return limit(() => build_request(client, payload, index, params, numberWorkers));
|
|
454
|
+
});
|
|
455
|
+
const complete = await Promise.allSettled(requestPromises);
|
|
456
|
+
const results = {
|
|
457
|
+
jobs_success: complete.filter((p) => p.status === "fulfilled").length,
|
|
458
|
+
jobs_fail: complete.filter((p) => p.status === "rejected").length
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
return results;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
async function build_request(client, payload, index, params, total) {
|
|
466
|
+
let retryAttempt = 0;
|
|
467
|
+
sLog(`DM4: summoning worker #${index} of ${total}`, params);
|
|
468
|
+
try {
|
|
469
|
+
const req = await client.request({
|
|
470
|
+
url: RUNTIME_URL + `?replicate=1&is_replica=true&runId=${params.runId || "no run id"}`,
|
|
471
|
+
method: "POST",
|
|
472
|
+
data: payload,
|
|
473
|
+
headers: {
|
|
474
|
+
"Content-Type": "text/plain",
|
|
475
|
+
},
|
|
476
|
+
timeout: 3600 * 1000 * 10,
|
|
477
|
+
retryConfig: {
|
|
478
|
+
retry: 3,
|
|
479
|
+
onRetryAttempt: (error) => {
|
|
480
|
+
const statusCode = error?.response?.status?.toString() || "";
|
|
481
|
+
retryAttempt++;
|
|
482
|
+
sLog(`DM4: summon worker ${index} retry #${retryAttempt}`, { statusCode, message: error.message, stack: error.stack, ...params }, "DEBUG");
|
|
483
|
+
},
|
|
484
|
+
retryDelay: 1000,
|
|
485
|
+
shouldRetry: (error) => {
|
|
486
|
+
if (error.code === 'ECONNRESET') return true;
|
|
487
|
+
const statusCode = error?.response?.status;
|
|
488
|
+
if (statusCode >= 500) return true;
|
|
489
|
+
if (statusCode === 429) return true;
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
});
|
|
493
|
+
sLog(`DM4: worker #${index} responded`, params);
|
|
494
|
+
const { data } = req;
|
|
495
|
+
return data;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
sLog(`DM4: worker #${index} failed to respond`, { message: error.message, stack: error.stack, code: error.code, retries: retryAttempt, ...params }, "ERROR");
|
|
498
|
+
return {};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
|
|
393
504
|
/*
|
|
394
505
|
------
|
|
395
506
|
MODELS
|
|
@@ -589,6 +700,8 @@ async function makeFunnel(funnel, user, firstEventTime, profile, scd, config) {
|
|
|
589
700
|
if (!user) throw new Error("no user");
|
|
590
701
|
if (!profile) profile = {};
|
|
591
702
|
if (!scd) scd = {};
|
|
703
|
+
const sessionStartEvents = config?.events?.filter(a => a.isSessionStartEvent) || [];
|
|
704
|
+
|
|
592
705
|
|
|
593
706
|
const chance = u.getChance();
|
|
594
707
|
const { hook = async (a) => a } = config;
|
|
@@ -728,6 +841,15 @@ async function makeFunnel(funnel, user, firstEventTime, profile, scd, config) {
|
|
|
728
841
|
|
|
729
842
|
const earliestTime = firstEventTime || dayjs(created).unix();
|
|
730
843
|
let funnelStartTime;
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
if (sessionStartEvents.length) {
|
|
847
|
+
const sessionStartEvent = chance.pickone(sessionStartEvents);
|
|
848
|
+
sessionStartEvent.relativeTimeMs = -15000;
|
|
849
|
+
funnelActualEventsWithOffset.push(sessionStartEvent);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
|
|
731
853
|
let finalEvents = await Promise.all(funnelActualEventsWithOffset
|
|
732
854
|
.map(async (event, index) => {
|
|
733
855
|
const newEvent = await makeEvent(distinct_id, earliestTime, event, anonymousIds, sessionIds, {}, groupKeys);
|
|
@@ -747,7 +869,6 @@ async function makeFunnel(funnel, user, firstEventTime, profile, scd, config) {
|
|
|
747
869
|
}
|
|
748
870
|
}));
|
|
749
871
|
|
|
750
|
-
|
|
751
872
|
await hook(finalEvents, "funnel-post", { user, profile, scd, funnel, config });
|
|
752
873
|
return [finalEvents, doesUserConvert];
|
|
753
874
|
}
|
|
@@ -1393,9 +1514,9 @@ function validateDungeonConfig(config) {
|
|
|
1393
1514
|
// funnels
|
|
1394
1515
|
|
|
1395
1516
|
// FUNNEL INFERENCE
|
|
1396
|
-
if (!funnels || !funnels.length) {
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1517
|
+
// if (!funnels || !funnels.length) {
|
|
1518
|
+
// funnels = inferFunnels(events);
|
|
1519
|
+
// }
|
|
1399
1520
|
|
|
1400
1521
|
if (alsoInferFunnels) {
|
|
1401
1522
|
const inferredFunnels = inferFunnels(events);
|
|
@@ -1502,13 +1623,21 @@ async function makeHookArray(arr = [], opts = {}) {
|
|
|
1502
1623
|
if (existsSync(dataFolder)) writeDir = dataFolder;
|
|
1503
1624
|
else writeDir = path.resolve("./");
|
|
1504
1625
|
|
|
1505
|
-
|
|
1626
|
+
// ! decide where to write the files in prod
|
|
1627
|
+
if (NODE_ENV === "prod") {
|
|
1628
|
+
writeDir = path.resolve(os.tmpdir());
|
|
1629
|
+
}
|
|
1630
|
+
if (typeof rest?.config?.writeToDisk === "string" && rest?.config?.writeToDisk?.startsWith('gs://')) {
|
|
1631
|
+
writeDir = rest.config.writeToDisk;
|
|
1632
|
+
}
|
|
1506
1633
|
|
|
1507
1634
|
function getWritePath() {
|
|
1508
1635
|
if (isBATCH_MODE) {
|
|
1636
|
+
if (writeDir?.startsWith('gs://')) return `${writeDir}/${filepath}-part-${batch.toString()}.${format}`;
|
|
1509
1637
|
return path.join(writeDir, `${filepath}-part-${batch.toString()}.${format}`);
|
|
1510
1638
|
}
|
|
1511
1639
|
else {
|
|
1640
|
+
if (writeDir?.startsWith('gs://')) return `${writeDir}/${filepath}.${format}`;
|
|
1512
1641
|
return path.join(writeDir, `${filepath}.${format}`);
|
|
1513
1642
|
}
|
|
1514
1643
|
}
|
|
@@ -1570,6 +1699,7 @@ async function makeHookArray(arr = [], opts = {}) {
|
|
|
1570
1699
|
}
|
|
1571
1700
|
if (isBATCH_MODE) data.length = 0;
|
|
1572
1701
|
return writeResult;
|
|
1702
|
+
|
|
1573
1703
|
}
|
|
1574
1704
|
|
|
1575
1705
|
async function flush() {
|