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.
Files changed (64) hide show
  1. package/dungeons/adspend.js +96 -0
  2. package/dungeons/anon.js +104 -0
  3. package/dungeons/big.js +225 -0
  4. package/dungeons/business.js +345 -0
  5. package/dungeons/complex.js +396 -0
  6. package/dungeons/experiments.js +125 -0
  7. package/dungeons/foobar.js +241 -0
  8. package/dungeons/funnels.js +272 -0
  9. package/dungeons/gaming.js +315 -0
  10. package/dungeons/media.js +7 -7
  11. package/dungeons/mirror.js +129 -0
  12. package/dungeons/sanity.js +113 -0
  13. package/dungeons/scd.js +205 -0
  14. package/dungeons/simple.js +195 -0
  15. package/dungeons/userAgent.js +190 -0
  16. package/entry.js +57 -0
  17. package/index.js +96 -68
  18. package/lib/cli/cli.js +10 -5
  19. package/lib/core/config-validator.js +28 -12
  20. package/lib/core/context.js +147 -130
  21. package/lib/core/storage.js +45 -31
  22. package/lib/data/defaults.js +2 -2
  23. package/lib/generators/adspend.js +1 -2
  24. package/lib/generators/events.js +35 -24
  25. package/lib/generators/funnels.js +1 -2
  26. package/lib/generators/mirror.js +1 -2
  27. package/lib/orchestrators/mixpanel-sender.js +15 -7
  28. package/lib/orchestrators/user-loop.js +21 -10
  29. package/lib/orchestrators/worker-manager.js +5 -2
  30. package/lib/utils/ai.js +36 -63
  31. package/lib/utils/chart.js +5 -0
  32. package/lib/utils/instructions.txt +593 -0
  33. package/lib/utils/utils.js +162 -38
  34. package/package.json +23 -9
  35. package/types.d.ts +376 -376
  36. package/.claude/settings.local.json +0 -20
  37. package/.gcloudignore +0 -18
  38. package/.gitattributes +0 -2
  39. package/.prettierrc +0 -0
  40. package/.vscode/launch.json +0 -80
  41. package/.vscode/settings.json +0 -69
  42. package/.vscode/tasks.json +0 -12
  43. package/dungeons/customers/.gitkeep +0 -0
  44. package/env.yaml +0 -1
  45. package/lib/cloud-function.js +0 -20
  46. package/scratch.mjs +0 -62
  47. package/scripts/dana.mjs +0 -137
  48. package/scripts/deploy.sh +0 -15
  49. package/scripts/jsdoctest.js +0 -5
  50. package/scripts/new-dungeon.sh +0 -98
  51. package/scripts/new-project.mjs +0 -14
  52. package/scripts/run-index.sh +0 -2
  53. package/scripts/update-deps.sh +0 -5
  54. package/tests/benchmark/concurrency.mjs +0 -52
  55. package/tests/cli.test.js +0 -124
  56. package/tests/coverage/.gitkeep +0 -0
  57. package/tests/e2e.test.js +0 -379
  58. package/tests/int.test.js +0 -715
  59. package/tests/testCases.mjs +0 -229
  60. package/tests/testSoup.mjs +0 -28
  61. package/tests/unit.test.js +0 -910
  62. package/tmp/.gitkeep +0 -0
  63. package/tsconfig.json +0 -18
  64. package/vitest.config.js +0 -47
@@ -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, clone } from 'ak-tools';
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
- value = value();
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
- // Now, if the resolved value is an array, use chance.pickone
205
- if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
206
- return chance.pickone(value);
207
- }
208
-
209
- if (Array.isArray(value) && value.every(item => typeof item === 'number')) {
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
- [array[i], array[j]] = [array[j], array[i]];
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
- let message = "";
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
- process.stdout.write(message);
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
- // Generate a single timestamp within this chunk using a normal distribution centered at chunkMid
971
- let offset;
972
- let iterations = 0;
973
- let isValidTime = false;
974
- do {
975
- iterations++;
976
- soupHits++;
977
- offset = chance.normal({ mean: mean, dev: chunkSize / deviation });
978
- isValidTime = validTime(chunkMid + offset, earliestTime, latestTime);
979
- if (iterations > 25000) {
980
- if (process.env?.NODE_ENV === 'dev') debugger;
981
- throw `${iterations} iterations... exceeded`;
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(chunkMid + offset).toISOString();
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.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 --no-warnings --experimental-vm-modules ./tests/benchmark/concurrency.mjs",
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": "./index.js"
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
- "@google/generative-ai": "^0.24.1",
54
- "ak-fetch": "^2.0.1",
55
- "ak-gemini": "^1.0.55",
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": {