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
@@ -7,7 +7,8 @@
7
7
 
8
8
  import dayjs from "dayjs";
9
9
  import path from "path";
10
- import { clone, comma, ls, rm } from "ak-tools";
10
+ import { comma, ls, rm } from "ak-tools";
11
+ import * as u from "../utils/utils.js";
11
12
  import mp from "mixpanel-import";
12
13
 
13
14
  /**
@@ -67,10 +68,11 @@ export async function sendToMixpanel(context) {
67
68
  // Import events
68
69
  if (eventData || isBATCH_MODE) {
69
70
  log(`importing events to mixpanel...\n`);
70
- let eventDataToImport = clone(eventData);
71
+ let eventDataToImport = u.deepClone(eventData);
71
72
  if (isBATCH_MODE) {
72
73
  const writeDir = eventData.getWriteDir();
73
74
  const files = await ls(writeDir.split(path.basename(writeDir)).join(""));
75
+ // @ts-ignore
74
76
  eventDataToImport = files.filter(f => f.includes('-EVENTS-'));
75
77
  }
76
78
  const imported = await mp(creds, eventDataToImport, {
@@ -84,10 +86,11 @@ export async function sendToMixpanel(context) {
84
86
  // Import user profiles
85
87
  if (userProfilesData || isBATCH_MODE) {
86
88
  log(`importing user profiles to mixpanel...\n`);
87
- let userProfilesToImport = clone(userProfilesData);
89
+ let userProfilesToImport = u.deepClone(userProfilesData);
88
90
  if (isBATCH_MODE) {
89
91
  const writeDir = userProfilesData.getWriteDir();
90
92
  const files = await ls(writeDir.split(path.basename(writeDir)).join(""));
93
+ // @ts-ignore
91
94
  userProfilesToImport = files.filter(f => f.includes('-USERS-'));
92
95
  }
93
96
  const imported = await mp(creds, userProfilesToImport, {
@@ -101,10 +104,11 @@ export async function sendToMixpanel(context) {
101
104
  // Import ad spend data
102
105
  if (groupEventData || isBATCH_MODE) {
103
106
  log(`importing ad spend data to mixpanel...\n`);
104
- let adSpendDataToImport = clone(adSpendData);
107
+ let adSpendDataToImport = u.deepClone(adSpendData);
105
108
  if (isBATCH_MODE) {
106
109
  const writeDir = adSpendData.getWriteDir();
107
110
  const files = await ls(writeDir.split(path.basename(writeDir)).join(""));
111
+ // @ts-ignore
108
112
  adSpendDataToImport = files.filter(f => f.includes('-AD-SPEND-'));
109
113
  }
110
114
  const imported = await mp(creds, adSpendDataToImport, {
@@ -120,10 +124,11 @@ export async function sendToMixpanel(context) {
120
124
  for (const groupEntity of groupProfilesData) {
121
125
  const groupKey = groupEntity?.groupKey;
122
126
  log(`importing ${groupKey} profiles to mixpanel...\n`);
123
- let groupProfilesToImport = clone(groupEntity);
127
+ let groupProfilesToImport = u.deepClone(groupEntity);
124
128
  if (isBATCH_MODE) {
125
129
  const writeDir = groupEntity.getWriteDir();
126
130
  const files = await ls(writeDir.split(path.basename(writeDir)).join(""));
131
+ // @ts-ignore
127
132
  groupProfilesToImport = files.filter(f => f.includes(`-GROUPS-${groupKey}`));
128
133
  }
129
134
  const imported = await mp({ token, groupKey }, groupProfilesToImport, {
@@ -138,10 +143,11 @@ export async function sendToMixpanel(context) {
138
143
  // Import group events
139
144
  if (groupEventData || isBATCH_MODE) {
140
145
  log(`importing group events to mixpanel...\n`);
141
- let groupEventDataToImport = clone(groupEventData);
146
+ let groupEventDataToImport = u.deepClone(groupEventData);
142
147
  if (isBATCH_MODE) {
143
148
  const writeDir = groupEventData.getWriteDir();
144
149
  const files = await ls(writeDir.split(path.basename(writeDir)).join(""));
150
+ // @ts-ignore
145
151
  groupEventDataToImport = files.filter(f => f.includes('-GROUP-EVENTS-'));
146
152
  }
147
153
  const imported = await mp(creds, groupEventDataToImport, {
@@ -160,10 +166,11 @@ export async function sendToMixpanel(context) {
160
166
  for (const scdEntity of scdTableData) {
161
167
  const scdKey = scdEntity?.scdKey;
162
168
  log(`importing ${scdKey} SCD data to mixpanel...\n`);
163
- let scdDataToImport = clone(scdEntity);
169
+ let scdDataToImport = u.deepClone(scdEntity);
164
170
  if (isBATCH_MODE) {
165
171
  const writeDir = scdEntity.getWriteDir();
166
172
  const files = await ls(writeDir.split(path.basename(writeDir)).join(""));
173
+ // @ts-ignore
167
174
  scdDataToImport = files.filter(f => f.includes(`-SCD-${scdKey}`));
168
175
  }
169
176
 
@@ -198,6 +205,7 @@ export async function sendToMixpanel(context) {
198
205
  if (!writeToDisk && isBATCH_MODE) {
199
206
  const writeDir = eventData?.getWriteDir() || userProfilesData?.getWriteDir();
200
207
  const listDir = await ls(writeDir.split(path.basename(writeDir)).join(""));
208
+ // @ts-ignore
201
209
  const files = listDir.filter(f =>
202
210
  f.includes('-EVENTS-') ||
203
211
  f.includes('-USERS-') ||
@@ -7,6 +7,7 @@
7
7
 
8
8
  import dayjs from "dayjs";
9
9
  import pLimit from 'p-limit';
10
+ import os from 'os';
10
11
  import * as u from "../utils/utils.js";
11
12
  import * as t from 'ak-tools';
12
13
  import { makeEvent } from "../generators/events.js";
@@ -22,7 +23,7 @@ import { makeSCD } from "../generators/scd.js";
22
23
  export async function userLoop(context) {
23
24
  const { config, storage, defaults } = context;
24
25
  const chance = u.getChance();
25
- const concurrency = config?.concurrency || 1;
26
+ const concurrency = config?.concurrency || Math.min(os.cpus().length * 2, 16);
26
27
  const USER_CONN = pLimit(concurrency);
27
28
 
28
29
  const {
@@ -45,8 +46,12 @@ export async function userLoop(context) {
45
46
  const avgEvPerUser = numEvents / numUsers;
46
47
  const startTime = Date.now();
47
48
 
49
+ // Create batches for parallel processing
50
+ const batchSize = Math.max(1, Math.ceil(numUsers / concurrency));
51
+ const userPromises = [];
52
+
48
53
  for (let i = 0; i < numUsers; i++) {
49
- await USER_CONN(async () => {
54
+ const userPromise = USER_CONN(async () => {
50
55
  context.incrementUserCount();
51
56
  const eps = Math.floor(context.getEventCount() / ((Date.now() - startTime) / 1000));
52
57
 
@@ -73,7 +78,7 @@ export async function userLoop(context) {
73
78
  : dayjs.unix(global.FIXED_BEGIN);
74
79
 
75
80
  if (hasLocation) {
76
- const location = u.shuffleArray(u.choose(defaults.locationsUsers)).pop();
81
+ const location = u.pickRandom(u.choose(defaults.locationsUsers));
77
82
  for (const key in location) {
78
83
  user[key] = location[key];
79
84
  }
@@ -83,6 +88,7 @@ export async function userLoop(context) {
83
88
  const profile = await makeUserProfile(context, userProps, user);
84
89
 
85
90
  // SCD creation
91
+ // @ts-ignore
86
92
  const scdUserTables = t.objFilter(scdProps, (scd) => scd.type === 'user' || !scd.type);
87
93
  const scdTableKeys = Object.keys(scdUserTables);
88
94
 
@@ -129,12 +135,12 @@ export async function userLoop(context) {
129
135
  const timeShift = context.getTimeShift();
130
136
  userFirstEventTime = dayjs(data[0].time).subtract(timeShift, 'seconds').unix();
131
137
  numEventsPreformed += data.length;
132
- usersEvents.push(...data);
138
+ usersEvents = usersEvents.concat(data);
133
139
 
134
140
  if (!userConverted) {
135
- if (verbose) {
136
- u.progress([["users", context.getUserCount()], ["events", context.getEventCount()]]);
137
- }
141
+ // if (verbose) {
142
+ // u.progress([["users", context.getUserCount()], ["events", context.getEventCount()]]);
143
+ // }
138
144
  return;
139
145
  }
140
146
  } else {
@@ -146,11 +152,11 @@ export async function userLoop(context) {
146
152
  const currentFunnel = chance.pickone(usageFunnels);
147
153
  const [data, userConverted] = await makeFunnel(context, currentFunnel, user, userFirstEventTime, profile, userSCD);
148
154
  numEventsPreformed += data.length;
149
- usersEvents.push(...data);
155
+ usersEvents = usersEvents.concat(data);
150
156
  } else {
151
157
  const data = await makeEvent(context, distinct_id, userFirstEventTime, u.pick(config.events), user.anonymousIds, user.sessionIds, {}, config.groupKeys, true);
152
158
  numEventsPreformed++;
153
- usersEvents.push(data);
159
+ usersEvents = usersEvents.concat(data);
154
160
  }
155
161
  }
156
162
 
@@ -187,8 +193,13 @@ export async function userLoop(context) {
187
193
  await eventData.hookPush(usersEvents, { profile });
188
194
 
189
195
  if (verbose) {
190
- u.progress([["users", context.getUserCount()], ["events", context.getEventCount()]]);
196
+ // u.progress([["users", context.getUserCount()], ["events", context.getEventCount()]]);
191
197
  }
192
198
  });
199
+
200
+ userPromises.push(userPromise);
193
201
  }
202
+
203
+ // Wait for all users to complete
204
+ await Promise.all(userPromises);
194
205
  }
@@ -3,6 +3,9 @@
3
3
  * Handles distributed processing across multiple cloud function workers
4
4
  */
5
5
 
6
+ /** @typedef {import('../../types.js').Context} Context */
7
+ /** @typedef {import('../../types.js').Dungeon} Dungeon */
8
+
6
9
  import pLimit from 'p-limit';
7
10
  import { GoogleAuth } from 'google-auth-library';
8
11
  import { timer, uid, sLog } from 'ak-tools';
@@ -138,7 +141,7 @@ export async function handleCloudFunctionEntry(req, res, mainFunction) {
138
141
  try {
139
142
  if (!script) throw new Error("no script");
140
143
 
141
- /** @type {Config} */
144
+ /** @type {Dungeon} */
142
145
  const config = eval(script);
143
146
 
144
147
  if (isReplica) {
@@ -148,7 +151,7 @@ export async function handleCloudFunctionEntry(req, res, mainFunction) {
148
151
  params.seed = newSeed;
149
152
  }
150
153
 
151
- /** @type {Config} */
154
+ /** @type {Dungeon} */
152
155
  const optionsYouCantChange = {
153
156
  verbose: false
154
157
  };
package/lib/utils/ai.js CHANGED
@@ -1,89 +1,62 @@
1
- import { GoogleGenerativeAI } from "@google/generative-ai";
2
- import * as u from "ak-tools";
3
1
 
4
- import 'dotenv/config';
2
+ /** @typedef {import('../../types.js').Dungeon} Dungeon */
3
+ /** @typedef {import('../../types.js').EventConfig} EventConfig */
5
4
 
5
+ import * as u from "ak-tools";
6
+ import 'dotenv/config';
6
7
  const { GEMINI_API_KEY: API_KEY, NODE_ENV = "unknown" } = process.env;
7
8
  if (!API_KEY) throw new Error("Please provide a Gemini API key");
9
+ import AITransformer from 'ak-gemini';
8
10
 
9
- async function generateSchema(userInput) {
10
- const gemini = new GoogleGenerativeAI(API_KEY);
11
- const model = gemini.getGenerativeModel({ model: "gemini-2.0-flash" });
12
- const PROOMPTY = await u.load("./components/prompt.txt");
13
- const prompt = `
14
- Given the following information about a website or app:
11
+ `build me a dungeon stream with these events and structure
15
12
 
16
- ${userInput}
13
+ { "event": "impression", "carousel": [{"product": "big mac"}] }
14
+ { "event": "viewed", "product_viewed": "big mac" }
15
+ { "event": "add to basket", "product_added": "big mac" }
16
+ { "event": "customized", "product_customized": "big mac" }
17
+ { "event": "checked out", "cart": [{"item": "big mac"}] }
17
18
 
18
- ${PROOMPTY}
19
19
 
20
- REMEMBER, YOUR INPUT IS:
20
+ but use all the different mcdonalds products as a possible values`
21
+ let CURRENT_PROMPT = `
21
22
 
22
- ${userInput}
23
- `.trim();
23
+ build me a dungeon with 250 unique event names ... they can be anything i don't really care make them from many verticals
24
24
 
25
- let schema;
26
- let schemaIsValid = false;
27
- let attempts = 0;
28
- do {
29
- attempts++;
30
- const result = await model.generateContent(prompt);
31
- const response = await result.response;
32
- const text = response.text();
33
- schema = processResponse(text);
34
- schemaIsValid = validator(schema);
35
- } while (!schemaIsValid);
36
25
 
37
- return schema;
38
- }
26
+ `;
39
27
 
40
- function processResponse(text) {
41
- let json;
42
- // check for ```json
43
- const start = text.indexOf("```json");
44
- const end = text.indexOf("```", start + 1);
45
28
 
46
- if (start === -1 || end === -1) {
47
- const start = text.indexOf("{");
48
- const end = text.lastIndexOf("}");
49
- json = text.slice(start, end + 1).trim();
50
- }
29
+ async function main(params) {
30
+ const { prompt } = params;
31
+ if (!prompt) throw new Error("Please provide a prompt");
32
+ const INSTRUCTIONS = await u.load('./lib/utils/instructions.txt', false);
33
+ const ai = new AITransformer({
34
+ apiKey: API_KEY,
35
+ onlyJSON: false,
36
+ systemInstructions: INSTRUCTIONS?.trim(),
51
37
 
52
- json = text.slice(start + 7, end).trim();
53
38
 
54
- try {
55
- return JSON.parse(json);
56
- }
57
- catch (e) {
58
- return null;
59
- }
39
+ });
40
+ await ai.init();
41
+ const response = await ai.message(prompt)
60
42
 
43
+ if (NODE_ENV === "dev") {
44
+ debugger;
45
+ }
61
46
 
62
- }
63
-
64
- function validator(schema) {
65
- let valid = true;
66
-
67
- //null schema are always invalid
68
- if (!schema) valid = false;
69
-
70
- //must have 3 or more events
71
- if (schema?.events?.length < 3) valid = false;
72
-
73
- //must have 2 or more superProps
74
- if (Object.keys(schema.superProps).length < 2) valid = false;
75
-
76
- //must have 2 or more userProps
77
- if (Object.keys(schema.userProps).length < 2) valid = false;
47
+ return response;
78
48
 
79
- return valid;
80
49
  }
81
50
 
82
51
 
83
- export { generateSchema };
52
+ export default main;
84
53
 
85
54
  if (import.meta.url === `file://${process.argv[1]}`) {
86
- generateSchema(`metube, a video streaming company like youutube, where users watch videos, search, like, comment, subscribe, share, create playlists, etc...`)
55
+ main(
56
+ {
57
+ prompt: CURRENT_PROMPT || "Generate a dungeon spec for a simple e-commerce site with checkout and add to cart events."
58
+ }
59
+ )
87
60
  .then((result) => {
88
61
  if (NODE_ENV === "dev") debugger;
89
62
  })
@@ -1,3 +1,7 @@
1
+ /** @typedef {import('../../types.js').EventSchema} EventSchema */
2
+ /** @typedef {import('../../types.js').Result} Result */
3
+ /** @typedef {import('../../types.js').Context} Context */
4
+
1
5
  import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
2
6
  import fs from 'fs';
3
7
  import * as u from 'ak-tools';
@@ -179,6 +183,7 @@ async function generateLineChart(rawData, signupEvents = ["sign up"], fileName)
179
183
  const imageBuffer = await chartJSNodeCanvas.renderToBuffer(configuration);
180
184
  const filePath = path.join(tempDir, `${fileName}.png`);
181
185
  const removed = await u.rm(filePath);
186
+ // @ts-ignore - imageBuffer is a Buffer but touch accepts it
182
187
  const file = await u.touch(filePath, imageBuffer);
183
188
 
184
189
  console.log(`Chart saved as ${fileName}.png`);