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/utils/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import Chance from 'chance';
|
|
3
3
|
import readline from 'readline';
|
|
4
|
-
import { comma, uid
|
|
4
|
+
import { comma, uid } from 'ak-tools';
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import dayjs from 'dayjs';
|
|
7
7
|
import utc from 'dayjs/plugin/utc.js';
|
|
@@ -186,10 +186,48 @@ function day(start, end) {
|
|
|
186
186
|
function choose(value) {
|
|
187
187
|
const chance = getChance();
|
|
188
188
|
|
|
189
|
+
// most of the time this will receive a list of strings;
|
|
190
|
+
// when that is the case, we need to ensure some 'keywords' like 'variant' or 'test' aren't in the array
|
|
191
|
+
// next we want to see if the array is unweighted ... i.e. no dupe strings and each string only occurs once ['a', 'b', 'c', 'd']
|
|
192
|
+
// if all these are true we will pickAWinner(value)()
|
|
193
|
+
if (Array.isArray(value) && value.length > 0 && value.every(item => typeof item === 'string')) {
|
|
194
|
+
// ensure terms 'variant' 'group' 'experiment' or 'population' are NOT in any of the items
|
|
195
|
+
if (!value.some(item => item.includes('variant') || item.includes('group') || item.includes('experiment') || item.includes('population'))) {
|
|
196
|
+
// check to make sure that each element in the array only occurs once...
|
|
197
|
+
const uniqueItems = new Set(value);
|
|
198
|
+
if (uniqueItems.size === value.length) {
|
|
199
|
+
// Array has no duplicates, use pickAWinner
|
|
200
|
+
return pickAWinner(value, 0)();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
189
210
|
try {
|
|
190
|
-
// Keep resolving the value if it's a function
|
|
211
|
+
// Keep resolving the value if it's a function (with caching)
|
|
191
212
|
while (typeof value === 'function') {
|
|
192
|
-
|
|
213
|
+
const funcString = value.toString();
|
|
214
|
+
|
|
215
|
+
// Check cache for weighted array functions
|
|
216
|
+
if (typeof global.weightedArrayCache === 'undefined') {
|
|
217
|
+
global.weightedArrayCache = new Map();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (global.weightedArrayCache.has(funcString)) {
|
|
221
|
+
value = global.weightedArrayCache.get(funcString);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const result = value();
|
|
226
|
+
if (Array.isArray(result) && result.length > 10) {
|
|
227
|
+
// Cache large arrays (likely weighted arrays)
|
|
228
|
+
global.weightedArrayCache.set(funcString, result);
|
|
229
|
+
}
|
|
230
|
+
value = result;
|
|
193
231
|
}
|
|
194
232
|
|
|
195
233
|
if (Array.isArray(value) && value.length === 0) {
|
|
@@ -201,13 +239,12 @@ function choose(value) {
|
|
|
201
239
|
return chance.pickone(value);
|
|
202
240
|
}
|
|
203
241
|
|
|
204
|
-
//
|
|
205
|
-
if (Array.isArray(value) && value.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return chance.pickone(value);
|
|
242
|
+
// PERFORMANCE: Optimized array handling - check first item type instead of every()
|
|
243
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
244
|
+
const firstType = typeof value[0];
|
|
245
|
+
if (firstType === 'string' || firstType === 'number') {
|
|
246
|
+
return chance.pickone(value);
|
|
247
|
+
}
|
|
211
248
|
}
|
|
212
249
|
|
|
213
250
|
if (Array.isArray(value) && value.every(item => typeof item === 'object')) {
|
|
@@ -666,6 +703,21 @@ function pickAWinner(items, mostChosenIndex) {
|
|
|
666
703
|
};
|
|
667
704
|
}
|
|
668
705
|
|
|
706
|
+
function quickHash(str, seed = 0) {
|
|
707
|
+
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
|
708
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
709
|
+
ch = str.charCodeAt(i);
|
|
710
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
711
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
712
|
+
}
|
|
713
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
|
714
|
+
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
715
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
|
716
|
+
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
717
|
+
|
|
718
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString();
|
|
719
|
+
};
|
|
720
|
+
|
|
669
721
|
/*
|
|
670
722
|
----
|
|
671
723
|
SHUFFLERS
|
|
@@ -677,11 +729,20 @@ function shuffleArray(array) {
|
|
|
677
729
|
const chance = getChance();
|
|
678
730
|
for (let i = array.length - 1; i > 0; i--) {
|
|
679
731
|
const j = chance.integer({ min: 0, max: i });
|
|
680
|
-
|
|
732
|
+
const temp = array[i];
|
|
733
|
+
array[i] = array[j];
|
|
734
|
+
array[j] = temp;
|
|
681
735
|
}
|
|
682
736
|
return array;
|
|
683
737
|
}
|
|
684
738
|
|
|
739
|
+
function pickRandom(array) {
|
|
740
|
+
if (!array || array.length === 0) return undefined;
|
|
741
|
+
// PERFORMANCE: Use Math.random() instead of chance.integer() for simple cases
|
|
742
|
+
const randomIndex = Math.floor(Math.random() * array.length);
|
|
743
|
+
return array[randomIndex];
|
|
744
|
+
}
|
|
745
|
+
|
|
685
746
|
function shuffleExceptFirst(array) {
|
|
686
747
|
if (array.length <= 1) return array;
|
|
687
748
|
const restShuffled = shuffleArray(array.slice(1));
|
|
@@ -877,16 +938,26 @@ function buildFileNames(config) {
|
|
|
877
938
|
* @param {[string, number][]} arrayOfArrays
|
|
878
939
|
*/
|
|
879
940
|
function progress(arrayOfArrays) {
|
|
941
|
+
const terminalWidth = process.stdout.columns || 120;
|
|
942
|
+
|
|
943
|
+
// Clear the entire line
|
|
880
944
|
readline.cursorTo(process.stdout, 0);
|
|
881
|
-
|
|
882
|
-
for (const status of arrayOfArrays) {
|
|
883
|
-
const [thing, p] = status;
|
|
884
|
-
message += `${thing} processed: ${comma(p)}\t\t`;
|
|
885
|
-
}
|
|
945
|
+
readline.clearLine(process.stdout, 0);
|
|
886
946
|
|
|
887
|
-
|
|
888
|
-
|
|
947
|
+
// Build message with better formatting
|
|
948
|
+
const items = arrayOfArrays.map(([thing, p]) => {
|
|
949
|
+
return `${thing}: ${comma(p)}`;
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
const message = items.join(' │ ');
|
|
889
953
|
|
|
954
|
+
// Ensure we don't exceed terminal width
|
|
955
|
+
const finalMessage = message.length > terminalWidth
|
|
956
|
+
? message.substring(0, terminalWidth - 3) + '...'
|
|
957
|
+
: message.padEnd(terminalWidth, ' ');
|
|
958
|
+
|
|
959
|
+
process.stdout.write(finalMessage);
|
|
960
|
+
}
|
|
890
961
|
|
|
891
962
|
function openFinder(path, callback) {
|
|
892
963
|
path = path || '/';
|
|
@@ -906,7 +977,6 @@ function getUniqueKeys(data) {
|
|
|
906
977
|
};
|
|
907
978
|
|
|
908
979
|
|
|
909
|
-
|
|
910
980
|
/*
|
|
911
981
|
----
|
|
912
982
|
CORE
|
|
@@ -953,7 +1023,7 @@ function TimeSoup(earliestTime, latestTime, peaks = 5, deviation = 2, mean = 0)
|
|
|
953
1023
|
let totalRange = latestTime - earliestTime;
|
|
954
1024
|
if (totalRange <= 0 || earliestTime > latestTime) {
|
|
955
1025
|
//just flip earliest and latest
|
|
956
|
-
let tempEarly = latestTime
|
|
1026
|
+
let tempEarly = latestTime;
|
|
957
1027
|
let tempLate = earliestTime;
|
|
958
1028
|
earliestTime = tempEarly;
|
|
959
1029
|
latestTime = tempLate;
|
|
@@ -967,23 +1037,22 @@ function TimeSoup(earliestTime, latestTime, peaks = 5, deviation = 2, mean = 0)
|
|
|
967
1037
|
const chunkEnd = chunkStart + chunkSize;
|
|
968
1038
|
const chunkMid = (chunkStart + chunkEnd) / 2;
|
|
969
1039
|
|
|
970
|
-
//
|
|
971
|
-
|
|
972
|
-
let
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
} while (chunkMid + offset < chunkStart || chunkMid + offset > chunkEnd);
|
|
1040
|
+
// Optimized timestamp generation - clamp to valid range instead of looping
|
|
1041
|
+
const maxDeviation = chunkSize / deviation;
|
|
1042
|
+
let offset = chance.normal({ mean: mean, dev: maxDeviation });
|
|
1043
|
+
|
|
1044
|
+
// Clamp to chunk boundaries to prevent infinite loops
|
|
1045
|
+
const proposedTime = chunkMid + offset;
|
|
1046
|
+
const clampedTime = Math.max(chunkStart, Math.min(chunkEnd, proposedTime));
|
|
1047
|
+
|
|
1048
|
+
// Ensure it's within the overall valid range
|
|
1049
|
+
const finalTime = Math.max(earliestTime, Math.min(latestTime, clampedTime));
|
|
1050
|
+
|
|
1051
|
+
// Update soup hits counter (keep for compatibility)
|
|
1052
|
+
soupHits++;
|
|
984
1053
|
|
|
985
1054
|
try {
|
|
986
|
-
return dayjs.unix(
|
|
1055
|
+
return dayjs.unix(finalTime).toISOString();
|
|
987
1056
|
}
|
|
988
1057
|
|
|
989
1058
|
catch (e) {
|
|
@@ -994,8 +1063,6 @@ function TimeSoup(earliestTime, latestTime, peaks = 5, deviation = 2, mean = 0)
|
|
|
994
1063
|
}
|
|
995
1064
|
|
|
996
1065
|
|
|
997
|
-
|
|
998
|
-
|
|
999
1066
|
/**
|
|
1000
1067
|
* @param {string} userId
|
|
1001
1068
|
* @param {number} bornDaysAgo=30
|
|
@@ -1151,6 +1218,61 @@ function generateEmoji(max = 10, array = false) {
|
|
|
1151
1218
|
};
|
|
1152
1219
|
};
|
|
1153
1220
|
|
|
1221
|
+
function deepClone(thing, opts) {
|
|
1222
|
+
// Handle primitives first (most common case)
|
|
1223
|
+
if (thing === null || thing === undefined) return thing;
|
|
1224
|
+
|
|
1225
|
+
const type = typeof thing;
|
|
1226
|
+
if (type !== 'object' && type !== 'function') {
|
|
1227
|
+
if (type === 'symbol') {
|
|
1228
|
+
return Symbol(thing.description);
|
|
1229
|
+
}
|
|
1230
|
+
return thing;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Handle arrays (common case)
|
|
1234
|
+
if (Array.isArray(thing)) {
|
|
1235
|
+
const result = new Array(thing.length);
|
|
1236
|
+
for (let i = 0; i < thing.length; i++) {
|
|
1237
|
+
result[i] = deepClone(thing[i], opts);
|
|
1238
|
+
}
|
|
1239
|
+
return result;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Handle other object types
|
|
1243
|
+
if (thing instanceof Date) return new Date(thing.getTime());
|
|
1244
|
+
if (thing instanceof RegExp) return new RegExp(thing.source, thing.flags);
|
|
1245
|
+
if (thing instanceof Function) {
|
|
1246
|
+
return opts && opts.newFns ?
|
|
1247
|
+
new Function('return ' + thing.toString())() :
|
|
1248
|
+
thing;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// Handle plain objects
|
|
1252
|
+
if (thing.constructor === Object) {
|
|
1253
|
+
const newObject = {};
|
|
1254
|
+
const keys = Object.keys(thing);
|
|
1255
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1256
|
+
const key = keys[i];
|
|
1257
|
+
newObject[key] = deepClone(thing[key], opts);
|
|
1258
|
+
}
|
|
1259
|
+
return newObject;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// Handle other object types
|
|
1263
|
+
try {
|
|
1264
|
+
return new thing.constructor(thing);
|
|
1265
|
+
} catch (e) {
|
|
1266
|
+
// Fallback for objects that can't be constructed this way
|
|
1267
|
+
const newObject = Object.create(Object.getPrototypeOf(thing));
|
|
1268
|
+
const keys = Object.keys(thing);
|
|
1269
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1270
|
+
const key = keys[i];
|
|
1271
|
+
newObject[key] = deepClone(thing[key], opts);
|
|
1272
|
+
}
|
|
1273
|
+
return newObject;
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1154
1276
|
|
|
1155
1277
|
|
|
1156
1278
|
export {
|
|
@@ -1159,13 +1281,14 @@ export {
|
|
|
1159
1281
|
dates,
|
|
1160
1282
|
day,
|
|
1161
1283
|
choose,
|
|
1284
|
+
pickRandom,
|
|
1162
1285
|
exhaust,
|
|
1163
1286
|
integer,
|
|
1164
1287
|
TimeSoup,
|
|
1165
1288
|
companyName,
|
|
1166
1289
|
generateEmoji,
|
|
1167
1290
|
hasSameKeys as haveSameKeys,
|
|
1168
|
-
|
|
1291
|
+
deepClone,
|
|
1169
1292
|
initChance,
|
|
1170
1293
|
getChance,
|
|
1171
1294
|
|
|
@@ -1182,6 +1305,7 @@ export {
|
|
|
1182
1305
|
getUniqueKeys,
|
|
1183
1306
|
person,
|
|
1184
1307
|
pickAWinner,
|
|
1308
|
+
quickHash,
|
|
1185
1309
|
weighArray,
|
|
1186
1310
|
weighFunnels,
|
|
1187
1311
|
validateEventConfig,
|
package/package.json
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-mp-data",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "builds all mixpanel primitives for a given project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "types.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"index.js",
|
|
10
|
+
"cli.js",
|
|
11
|
+
"types.d.ts",
|
|
12
|
+
"lib/",
|
|
13
|
+
"dungeons/",
|
|
14
|
+
"!dungeons/customers/",
|
|
15
|
+
"package.json",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
8
18
|
"scripts": {
|
|
9
19
|
"start": "node ./index.js",
|
|
10
20
|
"dev": "nodemon scratch.mjs --ignore ./data/*",
|
|
@@ -12,12 +22,16 @@
|
|
|
12
22
|
"post": "npm publish",
|
|
13
23
|
"deps": "./scripts/update-deps.sh",
|
|
14
24
|
"test": "NODE_ENV=test vitest run",
|
|
15
|
-
"test:watch": "NODE_ENV=test vitest",
|
|
16
|
-
"test:ui": "NODE_ENV=test vitest --ui",
|
|
17
25
|
"coverage": "vitest run --coverage && open ./coverage/index.html",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"typecheck:build": "tsc --noEmit --project tsconfig.build.json",
|
|
28
|
+
"typecheck:strict": "echo '⚠️ Running pedantic strict checks (many false positives expected)...' && tsc --noEmit --project tsconfig.build.json --strict",
|
|
29
|
+
"typecheck:summary": "echo '🔍 Type-checking all shipped files...' && npm run typecheck:build && echo '✅ All TypeScript checks passed!'",
|
|
18
30
|
"new:dungeon": "./scripts/new-dungeon.sh",
|
|
19
31
|
"new:project": "node ./scripts/new-project.mjs",
|
|
20
|
-
"exp:benchmark": "node
|
|
32
|
+
"exp:benchmark": "node ./tests/benchmark/concurrency.mjs",
|
|
33
|
+
"benchmark:phase1": "node ./tests/benchmark/phase1-performance.mjs",
|
|
34
|
+
"test:quick": "node ./tests/benchmark/quick-test.mjs",
|
|
21
35
|
"exp:soup": "node ./tests/testSoup.mjs",
|
|
22
36
|
"func:local": "functions-framework --target=entry",
|
|
23
37
|
"func:deploy": "./scripts/deploy.sh"
|
|
@@ -27,7 +41,7 @@
|
|
|
27
41
|
"url": "git+https://github.com/ak--47/make-mp-data.git"
|
|
28
42
|
},
|
|
29
43
|
"bin": {
|
|
30
|
-
"make-mp-data": "./
|
|
44
|
+
"make-mp-data": "./entry.js"
|
|
31
45
|
},
|
|
32
46
|
"keywords": [
|
|
33
47
|
"mixpanel",
|
|
@@ -50,10 +64,9 @@
|
|
|
50
64
|
"dependencies": {
|
|
51
65
|
"@google-cloud/functions-framework": "^3.4.2",
|
|
52
66
|
"@google-cloud/storage": "^7.14.0",
|
|
53
|
-
"
|
|
54
|
-
"ak-
|
|
55
|
-
"ak-
|
|
56
|
-
"ak-tools": "^1.1.0",
|
|
67
|
+
"ak-fetch": "^2.0.12",
|
|
68
|
+
"ak-gemini": "^1.0.59",
|
|
69
|
+
"ak-tools": "^1.1.1",
|
|
57
70
|
"chance": "^1.1.11",
|
|
58
71
|
"chart.js": "^3.9.1",
|
|
59
72
|
"chartjs-node-canvas": "^4.1.6",
|
|
@@ -67,6 +80,7 @@
|
|
|
67
80
|
"devDependencies": {
|
|
68
81
|
"@vitest/ui": "^2.1.9",
|
|
69
82
|
"nodemon": "^3.1.3",
|
|
83
|
+
"typescript": "^5.6.0",
|
|
70
84
|
"vitest": "^2.1.9"
|
|
71
85
|
},
|
|
72
86
|
"nodemonConfig": {
|