make-mp-data 2.0.0 ā 2.0.2
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/dungeons/adspend.js +96 -0
- package/dungeons/anon.js +104 -0
- package/dungeons/big.js +225 -0
- package/dungeons/business.js +345 -0
- package/dungeons/complex.js +396 -0
- package/dungeons/experiments.js +125 -0
- package/dungeons/foobar.js +241 -0
- package/dungeons/funnels.js +272 -0
- package/dungeons/gaming.js +315 -0
- package/dungeons/media.js +7 -7
- package/dungeons/mirror.js +129 -0
- package/dungeons/sanity.js +113 -0
- package/dungeons/scd.js +205 -0
- package/dungeons/simple.js +195 -0
- package/dungeons/userAgent.js +190 -0
- package/entry.js +57 -0
- package/index.js +96 -68
- package/lib/cli/cli.js +10 -5
- package/lib/core/config-validator.js +28 -12
- package/lib/core/context.js +147 -130
- package/lib/core/storage.js +45 -31
- package/lib/data/defaults.js +2 -2
- package/lib/generators/adspend.js +1 -2
- package/lib/generators/events.js +35 -24
- package/lib/generators/funnels.js +1 -2
- package/lib/generators/mirror.js +1 -2
- package/lib/orchestrators/mixpanel-sender.js +15 -7
- package/lib/orchestrators/user-loop.js +21 -10
- package/lib/orchestrators/worker-manager.js +5 -2
- package/lib/utils/ai.js +36 -63
- package/lib/utils/chart.js +5 -0
- package/lib/utils/instructions.txt +593 -0
- package/lib/utils/utils.js +162 -38
- package/package.json +23 -9
- package/types.d.ts +376 -376
- package/.claude/settings.local.json +0 -20
- package/.gcloudignore +0 -18
- package/.gitattributes +0 -2
- package/.prettierrc +0 -0
- package/.vscode/launch.json +0 -80
- package/.vscode/settings.json +0 -69
- package/.vscode/tasks.json +0 -12
- package/dungeons/customers/.gitkeep +0 -0
- package/env.yaml +0 -1
- package/lib/cloud-function.js +0 -20
- package/scratch.mjs +0 -62
- package/scripts/dana.mjs +0 -137
- package/scripts/deploy.sh +0 -15
- package/scripts/jsdoctest.js +0 -5
- package/scripts/new-dungeon.sh +0 -98
- package/scripts/new-project.mjs +0 -14
- package/scripts/run-index.sh +0 -2
- package/scripts/update-deps.sh +0 -5
- package/tests/benchmark/concurrency.mjs +0 -52
- package/tests/cli.test.js +0 -124
- package/tests/coverage/.gitkeep +0 -0
- package/tests/e2e.test.js +0 -379
- package/tests/int.test.js +0 -715
- package/tests/testCases.mjs +0 -229
- package/tests/testSoup.mjs +0 -28
- package/tests/unit.test.js +0 -910
- package/tmp/.gitkeep +0 -0
- package/tsconfig.json +0 -18
- package/vitest.config.js +0 -47
package/index.js
CHANGED
|
@@ -29,8 +29,6 @@ import { makeMirror } from './lib/generators/mirror.js';
|
|
|
29
29
|
import { makeGroupProfile, makeProfile } from './lib/generators/profiles.js';
|
|
30
30
|
|
|
31
31
|
// Utilities
|
|
32
|
-
import getCliParams from './lib/cli/cli.js';
|
|
33
|
-
import * as u from './lib/utils/utils.js';
|
|
34
32
|
import { generateLineChart } from './lib/utils/chart.js';
|
|
35
33
|
|
|
36
34
|
// External dependencies
|
|
@@ -39,6 +37,8 @@ import utc from "dayjs/plugin/utc.js";
|
|
|
39
37
|
import functions from '@google-cloud/functions-framework';
|
|
40
38
|
import { timer, sLog } from 'ak-tools';
|
|
41
39
|
import fs, { existsSync } from 'fs';
|
|
40
|
+
import path from 'path';
|
|
41
|
+
import { fileURLToPath } from 'url';
|
|
42
42
|
|
|
43
43
|
// Initialize dayjs and time constants
|
|
44
44
|
dayjs.extend(utc);
|
|
@@ -47,12 +47,65 @@ global.FIXED_NOW = FIXED_NOW;
|
|
|
47
47
|
let FIXED_BEGIN = dayjs.unix(FIXED_NOW).subtract(90, 'd').unix();
|
|
48
48
|
global.FIXED_BEGIN = FIXED_BEGIN;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Display configuration summary for CLI mode
|
|
52
|
+
* @param {Config} config - Validated configuration object
|
|
53
|
+
*/
|
|
54
|
+
function displayConfigurationSummary(config) {
|
|
55
|
+
console.log('\nš Configuration Summary');
|
|
56
|
+
console.log('ā'.repeat(40));
|
|
57
|
+
|
|
58
|
+
// Core parameters
|
|
59
|
+
console.log(`šÆ Target: ${config.numUsers?.toLocaleString()} users, ${config.numEvents?.toLocaleString()} events`);
|
|
60
|
+
console.log(`š
Timeline: ${config.numDays} days (${config.seed ? config.seed : 'random seed'})`);
|
|
61
|
+
console.log(`š¾ Output: ${config.format} format${config.region ? ` (${config.region})` : ''}`);
|
|
62
|
+
console.log(`ā” Performance: ${config.concurrency || 'auto'} threads`);
|
|
63
|
+
|
|
64
|
+
// Feature flags
|
|
65
|
+
const features = [];
|
|
66
|
+
if (config.hasAnonIds) features.push('anonymous IDs');
|
|
67
|
+
if (config.hasSessionIds) features.push('session IDs');
|
|
68
|
+
if (config.alsoInferFunnels) features.push('funnel inference');
|
|
69
|
+
if (config.makeChart) features.push('chart generation');
|
|
70
|
+
if (config.writeToDisk) features.push('disk output');
|
|
71
|
+
|
|
72
|
+
if (features.length > 0) {
|
|
73
|
+
console.log(`š§ Features: ${features.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Schema preview
|
|
77
|
+
if (config.events && config.events.length > 0) {
|
|
78
|
+
console.log('\nš Event Schema');
|
|
79
|
+
console.log('ā'.repeat(40));
|
|
80
|
+
const eventNames = config.events.slice(0, 6).map(e => e.event || e).join(', ');
|
|
81
|
+
const more = config.events.length > 6 ? ` (+${config.events.length - 6} more)` : '';
|
|
82
|
+
console.log(`š Events: ${eventNames}${more}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Funnels preview
|
|
86
|
+
if (config.funnels && config.funnels.length > 0) {
|
|
87
|
+
console.log(`š Funnels: ${config.funnels.length} funnel${config.funnels.length > 1 ? 's' : ''} configured`);
|
|
88
|
+
config.funnels.slice(0, 4).forEach((funnel, i) => {
|
|
89
|
+
if (funnel.sequence) {
|
|
90
|
+
const arrow = ' ā ';
|
|
91
|
+
const sequence = funnel.sequence.join(arrow);
|
|
92
|
+
const rate = funnel.conversionRate ? ` (${(funnel.conversionRate * 100).toFixed(0)}% conversion)` : '';
|
|
93
|
+
console.log(` ${i + 1}. ${sequence}${rate}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (config.funnels.length > 4) {
|
|
97
|
+
console.log(` ...and ${config.funnels.length - 4} more funnels`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Group analytics
|
|
102
|
+
if (config.groupKeys && config.groupKeys.length > 0) {
|
|
103
|
+
const groups = config.groupKeys.map(([key, count]) => `${count} ${key}s`).join(', ');
|
|
104
|
+
console.log(`š„ Groups: ${groups}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log(''); // Extra spacing before generation starts
|
|
108
|
+
}
|
|
56
109
|
|
|
57
110
|
/**
|
|
58
111
|
* Main data generation function
|
|
@@ -64,8 +117,9 @@ async function main(config) {
|
|
|
64
117
|
jobTimer.start();
|
|
65
118
|
|
|
66
119
|
//cli mode check for positional dungeon config
|
|
120
|
+
const isCLI = config._ && Array.isArray(config._);
|
|
67
121
|
if (isCLI) {
|
|
68
|
-
const firstArg = config._.slice().pop()
|
|
122
|
+
const firstArg = config._.slice().pop();
|
|
69
123
|
if (firstArg?.endsWith('.js') && existsSync(firstArg)) {
|
|
70
124
|
if (config.verbose) {
|
|
71
125
|
console.log(`\nš Loading dungeon config from: ${firstArg}`);
|
|
@@ -78,27 +132,36 @@ async function main(config) {
|
|
|
78
132
|
throw error;
|
|
79
133
|
}
|
|
80
134
|
}
|
|
81
|
-
|
|
135
|
+
|
|
82
136
|
}
|
|
83
137
|
|
|
138
|
+
if (config.verbose) console.log(`\nš§ Configuring dungeon with seed: ${config.seed}`);
|
|
84
139
|
let validatedConfig;
|
|
85
140
|
try {
|
|
86
141
|
// Step 1: Validate and enrich configuration
|
|
87
142
|
validatedConfig = validateDungeonConfig(config);
|
|
143
|
+
|
|
144
|
+
// Step 1.5: Display configuration summary (CLI mode only)
|
|
145
|
+
if (isCLI && validatedConfig.verbose) {
|
|
146
|
+
displayConfigurationSummary(validatedConfig);
|
|
147
|
+
}
|
|
88
148
|
|
|
89
149
|
// Step 2: Create context with validated config
|
|
90
|
-
const context = createContext(validatedConfig);
|
|
150
|
+
const context = createContext(validatedConfig, null, isCLI);
|
|
91
151
|
|
|
92
152
|
// Step 3: Initialize storage containers
|
|
93
153
|
const storageManager = new StorageManager(context);
|
|
94
154
|
const storage = await storageManager.initializeContainers();
|
|
95
155
|
updateContextWithStorage(context, storage);
|
|
96
156
|
|
|
157
|
+
// ! DATA GENERATION STARTS HERE
|
|
158
|
+
|
|
97
159
|
// Step 4: Generate ad spend data (if enabled)
|
|
98
160
|
if (validatedConfig.hasAdSpend) {
|
|
99
161
|
await generateAdSpendData(context);
|
|
100
162
|
}
|
|
101
163
|
|
|
164
|
+
if (context.config.verbose) console.log(`\nš Starting user and event generation...\n`);
|
|
102
165
|
// Step 5: Main user and event generation
|
|
103
166
|
await userLoop(context);
|
|
104
167
|
|
|
@@ -122,6 +185,10 @@ async function main(config) {
|
|
|
122
185
|
await makeMirror(context);
|
|
123
186
|
}
|
|
124
187
|
|
|
188
|
+
if (context.config.verbose) console.log(`\nā
Data generation completed successfully!\n`);
|
|
189
|
+
|
|
190
|
+
// ! DATA GENERATION ENDS HERE
|
|
191
|
+
|
|
125
192
|
// Step 10: Generate charts (if enabled)
|
|
126
193
|
if (validatedConfig.makeChart) {
|
|
127
194
|
await generateCharts(context);
|
|
@@ -155,11 +222,9 @@ async function main(config) {
|
|
|
155
222
|
};
|
|
156
223
|
|
|
157
224
|
} catch (error) {
|
|
158
|
-
if (
|
|
225
|
+
if (validatedConfig?.verbose) {
|
|
159
226
|
console.error(`\nā Error: ${error.message}\n`);
|
|
160
|
-
|
|
161
|
-
console.error(error.stack);
|
|
162
|
-
}
|
|
227
|
+
console.error(error.stack);
|
|
163
228
|
} else {
|
|
164
229
|
sLog("Main execution error", { error: error.message, stack: error.stack }, "ERROR");
|
|
165
230
|
}
|
|
@@ -195,7 +260,7 @@ async function generateGroupProfiles(context) {
|
|
|
195
260
|
const { config, storage } = context;
|
|
196
261
|
const { groupKeys, groupProps = {} } = config;
|
|
197
262
|
|
|
198
|
-
if (isCLI || config.verbose) {
|
|
263
|
+
if (context.isCLI() || config.verbose) {
|
|
199
264
|
console.log('\nš„ Generating group profiles...');
|
|
200
265
|
}
|
|
201
266
|
|
|
@@ -208,7 +273,7 @@ async function generateGroupProfiles(context) {
|
|
|
208
273
|
continue;
|
|
209
274
|
}
|
|
210
275
|
|
|
211
|
-
if (isCLI || config.verbose) {
|
|
276
|
+
if (context.isCLI() || config.verbose) {
|
|
212
277
|
console.log(` Creating ${groupCount.toLocaleString()} ${groupKey} profiles...`);
|
|
213
278
|
}
|
|
214
279
|
|
|
@@ -224,7 +289,7 @@ async function generateGroupProfiles(context) {
|
|
|
224
289
|
}
|
|
225
290
|
}
|
|
226
291
|
|
|
227
|
-
if (isCLI || config.verbose) {
|
|
292
|
+
if (context.isCLI() || config.verbose) {
|
|
228
293
|
console.log('ā
Group profiles generated successfully');
|
|
229
294
|
}
|
|
230
295
|
}
|
|
@@ -237,7 +302,7 @@ async function generateLookupTables(context) {
|
|
|
237
302
|
const { config, storage } = context;
|
|
238
303
|
const { lookupTables } = config;
|
|
239
304
|
|
|
240
|
-
if (isCLI || config.verbose) {
|
|
305
|
+
if (context.isCLI() || config.verbose) {
|
|
241
306
|
console.log('\nš Generating lookup tables...');
|
|
242
307
|
}
|
|
243
308
|
|
|
@@ -251,7 +316,7 @@ async function generateLookupTables(context) {
|
|
|
251
316
|
continue;
|
|
252
317
|
}
|
|
253
318
|
|
|
254
|
-
if (isCLI || config.verbose) {
|
|
319
|
+
if (context.isCLI() || config.verbose) {
|
|
255
320
|
console.log(` Creating ${entries.toLocaleString()} ${key} lookup entries...`);
|
|
256
321
|
}
|
|
257
322
|
|
|
@@ -264,7 +329,7 @@ async function generateLookupTables(context) {
|
|
|
264
329
|
}
|
|
265
330
|
}
|
|
266
331
|
|
|
267
|
-
if (isCLI || config.verbose) {
|
|
332
|
+
if (context.isCLI() || config.verbose) {
|
|
268
333
|
console.log('ā
Lookup tables generated successfully');
|
|
269
334
|
}
|
|
270
335
|
}
|
|
@@ -277,7 +342,7 @@ async function generateGroupSCDs(context) {
|
|
|
277
342
|
const { config, storage } = context;
|
|
278
343
|
const { scdProps, groupKeys } = config;
|
|
279
344
|
|
|
280
|
-
if (isCLI || config.verbose) {
|
|
345
|
+
if (context.isCLI() || config.verbose) {
|
|
281
346
|
console.log('\nš Generating group SCDs...');
|
|
282
347
|
}
|
|
283
348
|
|
|
@@ -300,7 +365,7 @@ async function generateGroupSCDs(context) {
|
|
|
300
365
|
continue; // No SCDs for this group type
|
|
301
366
|
}
|
|
302
367
|
|
|
303
|
-
if (isCLI || config.verbose) {
|
|
368
|
+
if (context.isCLI() || config.verbose) {
|
|
304
369
|
console.log(` Generating SCDs for ${groupCount.toLocaleString()} ${groupKey} entities...`);
|
|
305
370
|
}
|
|
306
371
|
|
|
@@ -342,7 +407,7 @@ async function generateGroupSCDs(context) {
|
|
|
342
407
|
}
|
|
343
408
|
}
|
|
344
409
|
|
|
345
|
-
if (isCLI || config.verbose) {
|
|
410
|
+
if (context.isCLI() || config.verbose) {
|
|
346
411
|
console.log('ā
Group SCDs generated successfully');
|
|
347
412
|
}
|
|
348
413
|
}
|
|
@@ -357,11 +422,11 @@ async function generateCharts(context) {
|
|
|
357
422
|
if (config.makeChart && storage.eventData?.length > 0) {
|
|
358
423
|
const chartPath = typeof config.makeChart === 'string'
|
|
359
424
|
? config.makeChart
|
|
360
|
-
:
|
|
425
|
+
: `./${config.simulationName}-timeline`;
|
|
361
426
|
|
|
362
427
|
await generateLineChart(storage.eventData, undefined, chartPath);
|
|
363
428
|
|
|
364
|
-
if (isCLI || config.verbose) {
|
|
429
|
+
if (context.isCLI() || config.verbose) {
|
|
365
430
|
console.log(`š Chart generated: ${chartPath}`);
|
|
366
431
|
} else {
|
|
367
432
|
sLog("Chart generated", { path: chartPath });
|
|
@@ -375,7 +440,7 @@ async function generateCharts(context) {
|
|
|
375
440
|
* @param {import('./types').Dungeon} config - Configuration object
|
|
376
441
|
*/
|
|
377
442
|
async function flushStorageToDisk(storage, config) {
|
|
378
|
-
if (
|
|
443
|
+
if (config.verbose) {
|
|
379
444
|
console.log('\nš¾ Writing data to disk...');
|
|
380
445
|
}
|
|
381
446
|
|
|
@@ -399,8 +464,8 @@ async function flushStorageToDisk(storage, config) {
|
|
|
399
464
|
|
|
400
465
|
await Promise.all(flushPromises);
|
|
401
466
|
|
|
402
|
-
if (
|
|
403
|
-
console.log('
|
|
467
|
+
if (config.verbose) {
|
|
468
|
+
console.log('š Data flushed to disk successfully');
|
|
404
469
|
}
|
|
405
470
|
}
|
|
406
471
|
|
|
@@ -445,44 +510,6 @@ function extractStorageData(storage) {
|
|
|
445
510
|
};
|
|
446
511
|
}
|
|
447
512
|
|
|
448
|
-
// CLI execution
|
|
449
|
-
if (isCLI) {
|
|
450
|
-
(async () => {
|
|
451
|
-
const cliConfig = getCliParams();
|
|
452
|
-
|
|
453
|
-
// Load dungeon config if --complex or --simple flags are used
|
|
454
|
-
let finalConfig = cliConfig;
|
|
455
|
-
if (cliConfig.complex) {
|
|
456
|
-
const complexConfig = await import('./dungeons/complex.js');
|
|
457
|
-
finalConfig = { ...complexConfig.default, ...cliConfig };
|
|
458
|
-
} else if (cliConfig.simple) {
|
|
459
|
-
const simpleConfig = await import('./dungeons/simple.js');
|
|
460
|
-
finalConfig = { ...simpleConfig.default, ...cliConfig };
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
main(finalConfig)
|
|
464
|
-
.then(result => {
|
|
465
|
-
console.log(`š Generated ${(result.eventCount || 0).toLocaleString()} events for ${(result.userCount || 0).toLocaleString()} users`);
|
|
466
|
-
console.log(`ā±ļø Total time: ${result.time?.human || 'unknown'}`);
|
|
467
|
-
if (result.files?.length) {
|
|
468
|
-
console.log(`š Files written: ${result.files.length}`);
|
|
469
|
-
if (cliConfig.verbose) {
|
|
470
|
-
result.files.forEach(file => console.log(` ${file}`));
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
console.log(`\nā
Job completed successfully!`);
|
|
474
|
-
process.exit(0);
|
|
475
|
-
})
|
|
476
|
-
.catch(error => {
|
|
477
|
-
console.error(`\nā Job failed: ${error.message}`);
|
|
478
|
-
if (cliConfig.verbose) {
|
|
479
|
-
console.error(error.stack);
|
|
480
|
-
}
|
|
481
|
-
process.exit(1);
|
|
482
|
-
});
|
|
483
|
-
})();
|
|
484
|
-
}
|
|
485
|
-
|
|
486
513
|
// Cloud Functions setup
|
|
487
514
|
functions.http('entry', async (req, res) => {
|
|
488
515
|
await handleCloudFunctionEntry(req, res, main);
|
|
@@ -494,4 +521,5 @@ export default main;
|
|
|
494
521
|
// CommonJS compatibility
|
|
495
522
|
if (typeof module !== 'undefined' && module.exports) {
|
|
496
523
|
module.exports = main;
|
|
497
|
-
}
|
|
524
|
+
}
|
|
525
|
+
|
package/lib/cli/cli.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import yargs from 'yargs';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
3
5
|
|
|
4
6
|
/** @typedef {import('../../types').Dungeon} Config */
|
|
5
7
|
|
|
6
|
-
const
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const packageJsonPath = path.join(__dirname, '../../package.json');
|
|
10
|
+
const { version } = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
7
11
|
|
|
8
12
|
const hero = String.raw`
|
|
9
13
|
|
|
@@ -24,9 +28,10 @@ function cliParams() {
|
|
|
24
28
|
.usage(`\nusage:\nnpx $0 [dataModel.js] [options]
|
|
25
29
|
|
|
26
30
|
examples:
|
|
27
|
-
npx $0
|
|
28
|
-
npx $0 --
|
|
29
|
-
npx $0
|
|
31
|
+
npx $0 (uses simple data model by default)
|
|
32
|
+
npx $0 --complex (full enterprise data model)
|
|
33
|
+
npx $0 --token 1234 --u 100 --e 1000 (send data to mixpanel)
|
|
34
|
+
npx $0 myDataConfig.js (use custom config)
|
|
30
35
|
|
|
31
36
|
DOCS: https://github.com/ak--47/make-mp-data
|
|
32
37
|
DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
|
|
@@ -95,7 +100,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
|
|
|
95
100
|
})
|
|
96
101
|
.option('concurrency', {
|
|
97
102
|
alias: 'conn',
|
|
98
|
-
default:
|
|
103
|
+
default: 10,
|
|
99
104
|
demandOption: false,
|
|
100
105
|
describe: 'concurrency level for data generation',
|
|
101
106
|
type: 'number'
|
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
* Extracted from index.js validateDungeonConfig function
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/** @typedef {import('../../types.js').Dungeon} Dungeon */
|
|
7
|
+
/** @typedef {import('../../types.js').EventConfig} EventConfig */
|
|
8
|
+
/** @typedef {import('../../types.js').Context} Context */
|
|
9
|
+
/** @typedef {import('../../types.js').Funnel} Funnel */
|
|
10
|
+
|
|
6
11
|
import dayjs from "dayjs";
|
|
7
|
-
import { makeName
|
|
12
|
+
import { makeName } from "ak-tools";
|
|
8
13
|
import * as u from "../utils/utils.js";
|
|
14
|
+
import os from "os";
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Infers funnels from the provided events
|
|
12
|
-
* @param {
|
|
13
|
-
* @returns {
|
|
18
|
+
* @param {EventConfig[]} events - Array of event configurations
|
|
19
|
+
* @returns {Funnel[]} Array of inferred funnel configurations
|
|
14
20
|
*/
|
|
15
21
|
function inferFunnels(events) {
|
|
16
22
|
const createdFunnels = [];
|
|
@@ -34,7 +40,7 @@ function inferFunnels(events) {
|
|
|
34
40
|
if (firstEvents.length) {
|
|
35
41
|
for (const event of firstEvents) {
|
|
36
42
|
createdFunnels.push({
|
|
37
|
-
...
|
|
43
|
+
...u.deepClone(funnelTemplate),
|
|
38
44
|
sequence: [event],
|
|
39
45
|
isFirstFunnel: true,
|
|
40
46
|
conversionRate: 100
|
|
@@ -43,12 +49,12 @@ function inferFunnels(events) {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
// At least one funnel with all usage events
|
|
46
|
-
createdFunnels.push({ ...
|
|
52
|
+
createdFunnels.push({ ...u.deepClone(funnelTemplate), sequence: usageEvents });
|
|
47
53
|
|
|
48
54
|
// Create random funnels for the rest
|
|
49
55
|
for (let i = 1; i < numFunnelsToCreate; i++) {
|
|
50
56
|
/** @type {import('../../types.js').Funnel} */
|
|
51
|
-
const funnel = { ...
|
|
57
|
+
const funnel = { ...u.deepClone(funnelTemplate) };
|
|
52
58
|
funnel.conversionRate = u.integer(25, 75);
|
|
53
59
|
funnel.timeToConvert = u.integer(1, 10);
|
|
54
60
|
funnel.weight = u.integer(1, 10);
|
|
@@ -63,8 +69,8 @@ function inferFunnels(events) {
|
|
|
63
69
|
|
|
64
70
|
/**
|
|
65
71
|
* Validates and enriches a dungeon configuration object
|
|
66
|
-
* @param {
|
|
67
|
-
* @returns {
|
|
72
|
+
* @param {Partial<Dungeon>} config - Raw configuration object
|
|
73
|
+
* @returns {Dungeon} Validated and enriched configuration
|
|
68
74
|
*/
|
|
69
75
|
export function validateDungeonConfig(config) {
|
|
70
76
|
const chance = u.getChance();
|
|
@@ -94,7 +100,7 @@ export function validateDungeonConfig(config) {
|
|
|
94
100
|
token = null,
|
|
95
101
|
region = "US",
|
|
96
102
|
writeToDisk = false,
|
|
97
|
-
verbose =
|
|
103
|
+
verbose = true,
|
|
98
104
|
makeChart = false,
|
|
99
105
|
soup = {},
|
|
100
106
|
hook = (record) => record,
|
|
@@ -110,9 +116,14 @@ export function validateDungeonConfig(config) {
|
|
|
110
116
|
alsoInferFunnels = false,
|
|
111
117
|
name = "",
|
|
112
118
|
batchSize = 500_000,
|
|
113
|
-
concurrency
|
|
119
|
+
concurrency
|
|
114
120
|
} = config;
|
|
115
121
|
|
|
122
|
+
// Set concurrency default only if not provided
|
|
123
|
+
if (concurrency === undefined || concurrency === null) {
|
|
124
|
+
concurrency = Math.min(os.cpus().length * 2, 16);
|
|
125
|
+
}
|
|
126
|
+
|
|
116
127
|
// Ensure defaults for deep objects
|
|
117
128
|
if (!config.superProps) config.superProps = superProps;
|
|
118
129
|
if (!config.userProps || Object.keys(config?.userProps || {})) config.userProps = userProps;
|
|
@@ -132,9 +143,9 @@ export function validateDungeonConfig(config) {
|
|
|
132
143
|
// Validate events
|
|
133
144
|
if (!events || !events.length) events = [{ event: "foo" }, { event: "bar" }, { event: "baz" }];
|
|
134
145
|
|
|
135
|
-
// Convert string events to objects
|
|
146
|
+
// Convert string events to objects
|
|
136
147
|
if (typeof events[0] === "string") {
|
|
137
|
-
events = events.map(e => ({ event: e }));
|
|
148
|
+
events = events.map(e => ({ event: /** @type {string} */ (e) }));
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
// Handle funnel inference
|
|
@@ -232,6 +243,11 @@ export function validateDungeonConfig(config) {
|
|
|
232
243
|
* @param {Object} config - Configuration to validate
|
|
233
244
|
* @throws {Error} If required fields are missing
|
|
234
245
|
*/
|
|
246
|
+
/**
|
|
247
|
+
* Validates required configuration parameters
|
|
248
|
+
* @param {Dungeon} config - Configuration object to validate
|
|
249
|
+
* @returns {boolean} True if validation passes
|
|
250
|
+
*/
|
|
235
251
|
export function validateRequiredConfig(config) {
|
|
236
252
|
if (!config) {
|
|
237
253
|
throw new Error("Configuration is required");
|