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
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import dayjs from "dayjs";
|
|
9
9
|
import path from "path";
|
|
10
|
-
import {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
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
|
-
|
|
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.
|
|
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.
|
|
138
|
+
usersEvents = usersEvents.concat(data);
|
|
133
139
|
|
|
134
140
|
if (!userConverted) {
|
|
135
|
-
if (verbose) {
|
|
136
|
-
|
|
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.
|
|
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.
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
but use all the different mcdonalds products as a possible values`
|
|
21
|
+
let CURRENT_PROMPT = `
|
|
21
22
|
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
52
|
+
export default main;
|
|
84
53
|
|
|
85
54
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
86
|
-
|
|
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
|
})
|
package/lib/utils/chart.js
CHANGED
|
@@ -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`);
|