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/lib/core/context.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Provides centralized state management and dependency injection
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/** @typedef {import('../../types').Dungeon} Dungeon */
|
|
7
|
-
/** @typedef {import('../../types').Storage} Storage */
|
|
8
|
-
/** @typedef {import('../../types').Context} Context */
|
|
9
|
-
/** @typedef {import('../../types').RuntimeState} RuntimeState */
|
|
10
|
-
/** @typedef {import('../../types').Defaults} Defaults */
|
|
6
|
+
/** @typedef {import('../../types.js').Dungeon} Dungeon */
|
|
7
|
+
/** @typedef {import('../../types.js').Storage} Storage */
|
|
8
|
+
/** @typedef {import('../../types.js').Context} Context */
|
|
9
|
+
/** @typedef {import('../../types.js').RuntimeState} RuntimeState */
|
|
10
|
+
/** @typedef {import('../../types.js').Defaults} Defaults */
|
|
11
11
|
|
|
12
12
|
import dayjs from "dayjs";
|
|
13
13
|
import { campaigns, devices, locations } from '../data/defaults.js';
|
|
@@ -20,26 +20,35 @@ import * as u from '../utils/utils.js';
|
|
|
20
20
|
* @returns {Defaults} Defaults object with factory functions
|
|
21
21
|
*/
|
|
22
22
|
function createDefaults(config, campaignData) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
23
|
+
const { singleCountry } = config;
|
|
24
|
+
|
|
25
|
+
// Pre-compute weighted arrays based on configuration
|
|
26
|
+
const locationsUsers = singleCountry ?
|
|
27
|
+
locations.filter(l => l.country === singleCountry) :
|
|
28
|
+
locations;
|
|
29
|
+
|
|
30
|
+
const locationsEvents = singleCountry ?
|
|
31
|
+
locations.filter(l => l.country === singleCountry) :
|
|
32
|
+
locations;
|
|
33
|
+
|
|
34
|
+
// PERFORMANCE: Pre-calculate weighted arrays to avoid repeated weighArray calls
|
|
35
|
+
const weighedLocationsUsers = u.weighArray(locationsUsers);
|
|
36
|
+
const weighedLocationsEvents = u.weighArray(locationsEvents);
|
|
37
|
+
const weighedIOSDevices = u.weighArray(devices.iosDevices);
|
|
38
|
+
const weighedAndroidDevices = u.weighArray(devices.androidDevices);
|
|
39
|
+
const weighedDesktopDevices = u.weighArray(devices.desktopDevices);
|
|
40
|
+
const weighedBrowsers = u.weighArray(devices.browsers);
|
|
41
|
+
const weighedCampaigns = u.weighArray(campaignData);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
locationsUsers: () => weighedLocationsUsers,
|
|
45
|
+
locationsEvents: () => weighedLocationsEvents,
|
|
46
|
+
iOSDevices: () => weighedIOSDevices,
|
|
47
|
+
androidDevices: () => weighedAndroidDevices,
|
|
48
|
+
desktopDevices: () => weighedDesktopDevices,
|
|
49
|
+
browsers: () => weighedBrowsers,
|
|
50
|
+
campaigns: () => weighedCampaigns
|
|
51
|
+
};
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
/**
|
|
@@ -47,107 +56,115 @@ function createDefaults(config, campaignData) {
|
|
|
47
56
|
* @returns {RuntimeState} Runtime state with counters and flags
|
|
48
57
|
*/
|
|
49
58
|
function createRuntimeState() {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
return {
|
|
60
|
+
operations: 0,
|
|
61
|
+
eventCount: 0,
|
|
62
|
+
userCount: 0,
|
|
63
|
+
isBatchMode: false,
|
|
64
|
+
verbose: false,
|
|
65
|
+
isCLI: false
|
|
66
|
+
};
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
/**
|
|
61
70
|
* Context factory that creates a complete context object for data generation
|
|
62
71
|
* @param {Dungeon} config - Validated configuration object
|
|
63
72
|
* @param {Storage|null} storage - Storage containers (optional, can be set later)
|
|
73
|
+
* @param {boolean} [isCliMode] - Whether running in CLI mode (optional, will detect if not provided)
|
|
64
74
|
* @returns {Context} Context object containing all state and dependencies
|
|
65
75
|
*/
|
|
66
|
-
export function createContext(config, storage = null) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
76
|
+
export function createContext(config, storage = null, isCliMode = null) {
|
|
77
|
+
// Import campaign data (could be made configurable)
|
|
78
|
+
const campaignData = campaigns;
|
|
79
|
+
|
|
80
|
+
// Create computed defaults based on config
|
|
81
|
+
const defaults = createDefaults(config, campaignData);
|
|
82
|
+
|
|
83
|
+
// Create runtime state
|
|
84
|
+
const runtime = createRuntimeState();
|
|
85
|
+
|
|
86
|
+
// Set runtime flags from config
|
|
87
|
+
runtime.verbose = config.verbose || false;
|
|
88
|
+
runtime.isBatchMode = config.batchSize && config.batchSize < config.numEvents;
|
|
89
|
+
runtime.isCLI = isCliMode !== null ? isCliMode : (process.argv[1]?.endsWith('index.js') || process.argv[1]?.endsWith('entry.js') || false);
|
|
90
|
+
if (runtime.isCLI) runtime.verbose = true; // Always verbose in CLI mode
|
|
91
|
+
|
|
92
|
+
const context = {
|
|
93
|
+
config,
|
|
94
|
+
storage,
|
|
95
|
+
defaults,
|
|
96
|
+
campaigns: campaignData,
|
|
97
|
+
runtime,
|
|
98
|
+
|
|
99
|
+
// Helper methods for updating state
|
|
100
|
+
incrementOperations() {
|
|
101
|
+
runtime.operations++;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
incrementEvents() {
|
|
105
|
+
runtime.eventCount++;
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
incrementUsers() {
|
|
109
|
+
runtime.userCount++;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
setStorage(storageObj) {
|
|
113
|
+
this.storage = storageObj;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// Getter methods for runtime state
|
|
117
|
+
getOperations() {
|
|
118
|
+
return runtime.operations;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
getEventCount() {
|
|
122
|
+
return runtime.eventCount;
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
getUserCount() {
|
|
126
|
+
return runtime.userCount;
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
incrementUserCount() {
|
|
130
|
+
runtime.userCount++;
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
incrementEventCount() {
|
|
134
|
+
runtime.eventCount++;
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
isBatchMode() {
|
|
138
|
+
return runtime.isBatchMode;
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
isCLI() {
|
|
142
|
+
return runtime.isCLI;
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Time helper methods
|
|
146
|
+
getTimeShift() {
|
|
147
|
+
const actualNow = dayjs().add(2, "day");
|
|
148
|
+
return actualNow.diff(dayjs.unix(this.FIXED_NOW), "seconds");
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
getDaysShift() {
|
|
152
|
+
const actualNow = dayjs().add(2, "day");
|
|
153
|
+
return actualNow.diff(dayjs.unix(this.FIXED_NOW), "days");
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Time constants (previously globals)
|
|
157
|
+
FIXED_NOW: global.FIXED_NOW,
|
|
158
|
+
FIXED_BEGIN: global.FIXED_BEGIN,
|
|
159
|
+
|
|
160
|
+
// PERFORMANCE: Pre-calculated time shift (instead of calculating per-event)
|
|
161
|
+
TIME_SHIFT_SECONDS: (() => {
|
|
162
|
+
const actualNow = dayjs().add(2, "day");
|
|
163
|
+
return actualNow.diff(dayjs.unix(global.FIXED_NOW), "seconds");
|
|
164
|
+
})(),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return context;
|
|
151
168
|
}
|
|
152
169
|
|
|
153
170
|
/**
|
|
@@ -157,8 +174,8 @@ export function createContext(config, storage = null) {
|
|
|
157
174
|
* @returns {Context} Updated context object
|
|
158
175
|
*/
|
|
159
176
|
export function updateContextWithStorage(context, storage) {
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
context.storage = storage;
|
|
178
|
+
return context;
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
/**
|
|
@@ -167,14 +184,14 @@ export function updateContextWithStorage(context, storage) {
|
|
|
167
184
|
* @throws {Error} If context is missing required properties
|
|
168
185
|
*/
|
|
169
186
|
export function validateContext(context) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
const required = ['config', 'defaults', 'campaigns', 'runtime'];
|
|
188
|
+
const missing = required.filter(prop => !context[prop]);
|
|
189
|
+
|
|
190
|
+
if (missing.length > 0) {
|
|
191
|
+
throw new Error(`Context is missing required properties: ${missing.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!context.config.numUsers || !context.config.numEvents) {
|
|
195
|
+
throw new Error('Context config must have numUsers and numEvents');
|
|
196
|
+
}
|
|
180
197
|
}
|
package/lib/core/storage.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* Extracted from index.js to eliminate global dependencies
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/** @typedef {import('../../types').Context} Context */
|
|
6
|
+
/** @typedef {import('../../types.js').Context} Context */
|
|
7
|
+
/** @typedef {import('../../types.js').HookedArray<any>} HookedArray */
|
|
8
|
+
/** @typedef {import('../../types.js').Storage} Storage */
|
|
9
|
+
/** @typedef {import('../../types.js').hookArrayOptions<any>} hookArrayOptions */
|
|
7
10
|
|
|
8
11
|
import { existsSync } from "fs";
|
|
9
12
|
import pLimit from 'p-limit';
|
|
@@ -14,25 +17,19 @@ import * as u from "../utils/utils.js";
|
|
|
14
17
|
/**
|
|
15
18
|
* Creates a hooked array that transforms data on push and handles batching/disk writes
|
|
16
19
|
* @param {Array} arr - Base array to enhance
|
|
17
|
-
* @param {
|
|
18
|
-
* @
|
|
19
|
-
* @param {string} opts.type - Type identifier for the hook function
|
|
20
|
-
* @param {string} opts.filepath - Base filename for disk writes
|
|
21
|
-
* @param {string} opts.format - Output format ('csv' or 'json')
|
|
22
|
-
* @param {number} opts.concurrency - Max concurrent file operations
|
|
23
|
-
* @param {Context} opts.context - Context object with config, batchSize, etc.
|
|
24
|
-
* @returns {Promise<Array>} Enhanced array with hookPush and flush methods
|
|
20
|
+
* @param {hookArrayOptions} opts - Configuration options
|
|
21
|
+
* @returns {Promise<HookedArray>} Enhanced array with hookPush and flush methods
|
|
25
22
|
*/
|
|
26
|
-
export async function createHookArray(arr = [], opts
|
|
23
|
+
export async function createHookArray(arr = [], opts) {
|
|
27
24
|
const {
|
|
28
25
|
hook = a => a,
|
|
29
26
|
type = "",
|
|
30
27
|
filepath = "./defaultFile",
|
|
31
28
|
format = "csv",
|
|
32
29
|
concurrency = 1,
|
|
33
|
-
context = {},
|
|
30
|
+
context = /** @type {Context} */ ({}),
|
|
34
31
|
...rest
|
|
35
|
-
} = opts;
|
|
32
|
+
} = opts || {};
|
|
36
33
|
|
|
37
34
|
const FILE_CONN = pLimit(concurrency);
|
|
38
35
|
const { config = {}, runtime = {} } = context;
|
|
@@ -75,28 +72,41 @@ export async function createHookArray(arr = [], opts = {}) {
|
|
|
75
72
|
if (item === null || item === undefined) return false;
|
|
76
73
|
if (typeof item === 'object' && Object.keys(item).length === 0) return false;
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
// Performance optimization: skip hook overhead for passthrough hooks
|
|
76
|
+
const isPassthroughHook = hook.toString().includes('return record') || hook.length === 1;
|
|
79
77
|
|
|
80
|
-
if (
|
|
81
|
-
for
|
|
78
|
+
if (isPassthroughHook) {
|
|
79
|
+
// Fast path for passthrough hooks - no transformation needed
|
|
80
|
+
if (Array.isArray(item)) {
|
|
81
|
+
arr.push(...item);
|
|
82
|
+
} else {
|
|
83
|
+
arr.push(item);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// Slow path for actual transformation hooks
|
|
87
|
+
const allMetaData = { ...rest, ...meta };
|
|
88
|
+
|
|
89
|
+
if (Array.isArray(item)) {
|
|
90
|
+
for (const i of item) {
|
|
91
|
+
try {
|
|
92
|
+
const enriched = await hook(i, type, allMetaData);
|
|
93
|
+
if (Array.isArray(enriched)) enriched.forEach(e => arr.push(e));
|
|
94
|
+
else arr.push(enriched);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.error(`\n\nyour hook had an error\n\n`, e);
|
|
97
|
+
arr.push(i);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
82
101
|
try {
|
|
83
|
-
const enriched = await hook(
|
|
102
|
+
const enriched = await hook(item, type, allMetaData);
|
|
84
103
|
if (Array.isArray(enriched)) enriched.forEach(e => arr.push(e));
|
|
85
104
|
else arr.push(enriched);
|
|
86
105
|
} catch (e) {
|
|
87
106
|
console.error(`\n\nyour hook had an error\n\n`, e);
|
|
88
|
-
arr.push(
|
|
107
|
+
arr.push(item);
|
|
89
108
|
}
|
|
90
109
|
}
|
|
91
|
-
} else {
|
|
92
|
-
try {
|
|
93
|
-
const enriched = await hook(item, type, allMetaData);
|
|
94
|
-
if (Array.isArray(enriched)) enriched.forEach(e => arr.push(e));
|
|
95
|
-
else arr.push(enriched);
|
|
96
|
-
} catch (e) {
|
|
97
|
-
console.error(`\n\nyour hook had an error\n\n`, e);
|
|
98
|
-
arr.push(item);
|
|
99
|
-
}
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
if (arr.length > BATCH_SIZE) {
|
|
@@ -105,6 +115,8 @@ export async function createHookArray(arr = [], opts = {}) {
|
|
|
105
115
|
batch++;
|
|
106
116
|
const writePath = getWritePath();
|
|
107
117
|
const writeResult = await FILE_CONN(() => writeToDisk(arr, { writePath }));
|
|
118
|
+
// Ensure array is cleared after successful write
|
|
119
|
+
arr.length = 0;
|
|
108
120
|
return writeResult;
|
|
109
121
|
} else {
|
|
110
122
|
return Promise.resolve(false);
|
|
@@ -116,7 +128,7 @@ export async function createHookArray(arr = [], opts = {}) {
|
|
|
116
128
|
let writeResult;
|
|
117
129
|
|
|
118
130
|
if (config.verbose) {
|
|
119
|
-
console.log(`\n\
|
|
131
|
+
console.log(`\n\twriting ${writePath}\n`);
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
switch (format) {
|
|
@@ -130,7 +142,7 @@ export async function createHookArray(arr = [], opts = {}) {
|
|
|
130
142
|
throw new Error(`format ${format} is not supported`);
|
|
131
143
|
}
|
|
132
144
|
|
|
133
|
-
|
|
145
|
+
// Array clearing now handled in transformThenPush to ensure proper timing
|
|
134
146
|
return writeResult;
|
|
135
147
|
}
|
|
136
148
|
|
|
@@ -144,7 +156,8 @@ export async function createHookArray(arr = [], opts = {}) {
|
|
|
144
156
|
}
|
|
145
157
|
|
|
146
158
|
// Enhance the array with our methods
|
|
147
|
-
|
|
159
|
+
/** @type {HookedArray} */
|
|
160
|
+
const enrichedArray = /** @type {any} */ (arr);
|
|
148
161
|
enrichedArray.hookPush = transformThenPush;
|
|
149
162
|
enrichedArray.flush = flush;
|
|
150
163
|
enrichedArray.getWriteDir = getWriteDir;
|
|
@@ -168,11 +181,12 @@ export class StorageManager {
|
|
|
168
181
|
|
|
169
182
|
/**
|
|
170
183
|
* Initialize all storage containers for the data generation process
|
|
171
|
-
* @returns {
|
|
184
|
+
* @returns {Promise<Storage>} Storage containers object
|
|
172
185
|
*/
|
|
173
186
|
async initializeContainers() {
|
|
174
187
|
const { config } = this.context;
|
|
175
188
|
|
|
189
|
+
/** @type {Storage} */
|
|
176
190
|
const storage = {
|
|
177
191
|
eventData: await createHookArray([], {
|
|
178
192
|
hook: config.hook,
|
|
@@ -194,7 +208,7 @@ export class StorageManager {
|
|
|
194
208
|
|
|
195
209
|
adSpendData: await createHookArray([], {
|
|
196
210
|
hook: config.hook,
|
|
197
|
-
type: "
|
|
211
|
+
type: "ad-spend",
|
|
198
212
|
filepath: `${config.simulationName || 'adspend'}-ADSPEND`,
|
|
199
213
|
format: config.format || "csv",
|
|
200
214
|
concurrency: config.concurrency || 1,
|
package/lib/data/defaults.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @fileoverview Contains default values for campaigns, devices, locations, and domains
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/** @typedef {import('../../types.
|
|
8
|
-
/** @typedef {import('../../types.
|
|
7
|
+
/** @typedef {import('../../types.js').Dungeon} Config */
|
|
8
|
+
/** @typedef {import('../../types.js').ValueValid} ValueValid */
|
|
9
9
|
|
|
10
10
|
//? https://docs.mixpanel.com/docs/data-structure/property-reference#default-properties
|
|
11
11
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
/** @typedef {import('../../types').Context} Context */
|
|
7
7
|
|
|
8
8
|
import dayjs from "dayjs";
|
|
9
|
-
import { md5 } from "ak-tools";
|
|
10
9
|
import * as u from "../utils/utils.js";
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -74,7 +73,7 @@ function createAdSpendEvent(network, campaign, day, chance) {
|
|
|
74
73
|
|
|
75
74
|
// Create unique identifiers
|
|
76
75
|
const id = network.utm_source[0] + '-' + campaign;
|
|
77
|
-
const uid =
|
|
76
|
+
const uid = u.quickHash(id);
|
|
78
77
|
|
|
79
78
|
return {
|
|
80
79
|
event: "$ad_spend",
|
package/lib/generators/events.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
/** @typedef {import('../../types').Context} Context */
|
|
11
11
|
|
|
12
12
|
import dayjs from "dayjs";
|
|
13
|
-
import { md5 } from "ak-tools";
|
|
14
13
|
import * as u from "../utils/utils.js";
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -77,7 +76,7 @@ export async function makeEvent(
|
|
|
77
76
|
|
|
78
77
|
// Add default properties based on configuration
|
|
79
78
|
if (hasLocation) {
|
|
80
|
-
defaultProps.location = u.
|
|
79
|
+
defaultProps.location = u.pickRandom(defaults.locationsEvents());
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
if (hasBrowser) {
|
|
@@ -91,31 +90,34 @@ export async function makeEvent(
|
|
|
91
90
|
|
|
92
91
|
// Add campaigns with attribution likelihood
|
|
93
92
|
if (hasCampaigns && chance.bool({ likelihood: 25 })) {
|
|
94
|
-
defaultProps.campaigns = u.
|
|
93
|
+
defaultProps.campaigns = u.pickRandom(defaults.campaigns());
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
// Select device from pool
|
|
98
97
|
const devices = devicePool.flat();
|
|
99
98
|
if (devices.length) {
|
|
100
|
-
defaultProps.device = u.
|
|
99
|
+
defaultProps.device = u.pickRandom(devices);
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
// Set event time using TimeSoup for realistic distribution
|
|
104
103
|
if (earliestTime) {
|
|
105
104
|
if (isFirstEvent) {
|
|
106
|
-
|
|
105
|
+
// Apply time shift to move to present day using precomputed value
|
|
106
|
+
eventTemplate.time = dayjs.unix(earliestTime).add(context.TIME_SHIFT_SECONDS, 'seconds').toISOString();
|
|
107
107
|
} else {
|
|
108
|
-
|
|
108
|
+
// Get time from TimeSoup and apply precomputed time shift
|
|
109
|
+
const soupTime = u.TimeSoup(earliestTime, context.FIXED_NOW, peaks, deviation, mean);
|
|
110
|
+
eventTemplate.time = dayjs(soupTime).add(context.TIME_SHIFT_SECONDS, 'seconds').toISOString();
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
// Add anonymous and session identifiers
|
|
113
115
|
if (anonymousIds.length) {
|
|
114
|
-
eventTemplate.device_id =
|
|
116
|
+
eventTemplate.device_id = u.pickRandom(anonymousIds);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
if (sessionIds.length) {
|
|
118
|
-
eventTemplate.session_id =
|
|
120
|
+
eventTemplate.session_id = u.pickRandom(sessionIds);
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
// Sometimes add user_id (for attribution modeling)
|
|
@@ -128,16 +130,28 @@ export async function makeEvent(
|
|
|
128
130
|
eventTemplate.user_id = distinct_id;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
//
|
|
132
|
-
const props = { ...chosenEvent.properties, ...superProps };
|
|
133
|
-
|
|
133
|
+
// PERFORMANCE: Process properties directly without creating intermediate object
|
|
134
134
|
// Add custom properties from event configuration
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
if (chosenEvent.properties) {
|
|
136
|
+
for (const key in chosenEvent.properties) {
|
|
137
|
+
try {
|
|
138
|
+
eventTemplate[key] = u.choose(chosenEvent.properties[key]);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error(`error with ${key} in ${chosenEvent.event} event`, e);
|
|
141
|
+
// Continue processing other properties
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Add super properties (override event properties if needed)
|
|
147
|
+
if (superProps) {
|
|
148
|
+
for (const key in superProps) {
|
|
149
|
+
try {
|
|
150
|
+
eventTemplate[key] = u.choose(superProps[key]);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.error(`error with ${key} in super props`, e);
|
|
153
|
+
// Continue processing other properties
|
|
154
|
+
}
|
|
141
155
|
}
|
|
142
156
|
}
|
|
143
157
|
|
|
@@ -150,14 +164,11 @@ export async function makeEvent(
|
|
|
150
164
|
addGroupProperties(eventTemplate, groupKeys);
|
|
151
165
|
|
|
152
166
|
// Generate unique insert_id
|
|
153
|
-
|
|
167
|
+
const distinctId = eventTemplate.user_id || eventTemplate.device_id || eventTemplate.distinct_id || distinct_id;
|
|
168
|
+
const tuple = `${eventTemplate.event}-${eventTemplate.time}-${distinctId}`;
|
|
169
|
+
eventTemplate.insert_id = u.quickHash(tuple);
|
|
154
170
|
|
|
155
|
-
//
|
|
156
|
-
if (earliestTime) {
|
|
157
|
-
const timeShift = dayjs().add(2, "day").diff(dayjs.unix(context.FIXED_NOW), "seconds");
|
|
158
|
-
const timeShifted = dayjs(eventTemplate.time).add(timeShift, "seconds").toISOString();
|
|
159
|
-
eventTemplate.time = timeShifted;
|
|
160
|
-
}
|
|
171
|
+
// Note: Time shift already applied above during timestamp calculation
|
|
161
172
|
|
|
162
173
|
return eventTemplate;
|
|
163
174
|
}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
/** @typedef {import('../../types').Context} Context */
|
|
7
7
|
|
|
8
8
|
import dayjs from "dayjs";
|
|
9
|
-
import { clone } from "ak-tools";
|
|
10
9
|
import * as u from "../utils/utils.js";
|
|
11
10
|
import { makeEvent } from "./events.js";
|
|
12
11
|
|
|
@@ -129,7 +128,7 @@ function buildFunnelEvents(context, sequence, chosenFunnelProps) {
|
|
|
129
128
|
|
|
130
129
|
return sequence.map((eventName) => {
|
|
131
130
|
const foundEvent = config.events?.find((e) => e.event === eventName);
|
|
132
|
-
const eventSpec =
|
|
131
|
+
const eventSpec = u.deepClone(foundEvent) || { event: eventName, properties: {} };
|
|
133
132
|
|
|
134
133
|
// Process event properties
|
|
135
134
|
for (const key in eventSpec.properties) {
|
package/lib/generators/mirror.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
/** @typedef {import('../../types').Context} Context */
|
|
7
7
|
|
|
8
8
|
import dayjs from "dayjs";
|
|
9
|
-
import { clone } from "ak-tools";
|
|
10
9
|
import * as u from "../utils/utils.js";
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -44,7 +43,7 @@ export async function makeMirror(context) {
|
|
|
44
43
|
if (shouldProcessEvent(oldEvent.event, events)) {
|
|
45
44
|
// Clone event only when needed
|
|
46
45
|
if (!newEvent) {
|
|
47
|
-
newEvent =
|
|
46
|
+
newEvent = u.deepClone(oldEvent);
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
// Apply the specified strategy
|