make-mp-data 1.5.1 → 1.5.3

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 (40) hide show
  1. package/.gcloudignore +17 -0
  2. package/.vscode/launch.json +37 -14
  3. package/.vscode/settings.json +2 -0
  4. package/.vscode/tasks.json +12 -0
  5. package/components/ai.js +93 -0
  6. package/components/chart.js +14 -0
  7. package/components/cli.js +8 -2
  8. package/components/project.js +11 -0
  9. package/components/prompt.txt +98 -0
  10. package/components/utils.js +126 -5
  11. package/{schemas → dungeons}/adspend.js +1 -1
  12. package/{schemas → dungeons}/anon.js +1 -1
  13. package/{schemas → dungeons}/big.js +1 -1
  14. package/{schemas → dungeons}/business.js +1 -1
  15. package/{schemas → dungeons}/complex.js +9 -9
  16. package/dungeons/foobar.js +241 -0
  17. package/{schemas → dungeons}/funnels.js +2 -3
  18. package/dungeons/gaming.js +314 -0
  19. package/{schemas → dungeons}/mirror.js +1 -1
  20. package/{schemas → dungeons}/sanity.js +1 -1
  21. package/dungeons/scd.js +205 -0
  22. package/dungeons/session-replay.js +175 -0
  23. package/{schemas → dungeons}/simple.js +1 -1
  24. package/dungeons/userAgent.js +190 -0
  25. package/env.yaml +1 -0
  26. package/index.js +453 -154
  27. package/package.json +9 -5
  28. package/scripts/deploy.sh +11 -0
  29. package/scripts/new-dungeon.sh +10 -4
  30. package/tests/benchmark/concurrency.mjs +2 -2
  31. package/tests/cli.test.js +121 -0
  32. package/tests/e2e.test.js +134 -186
  33. package/tests/int.test.js +3 -2
  34. package/tests/jest.config.js +8 -0
  35. package/tests/unit.test.js +1 -1
  36. package/tsconfig.json +1 -1
  37. package/types.d.ts +40 -9
  38. package/schemas/foobar.js +0 -125
  39. package/schemas/session-replay.js +0 -136
  40. /package/dungeons/{.gitkeep → customers/.gitkeep} +0 -0
package/.gcloudignore ADDED
@@ -0,0 +1,17 @@
1
+ .env
2
+ scratch.mjs
3
+ tsconfig.json
4
+ types.d.ts
5
+ tmp
6
+ tests
7
+ scripts
8
+ data
9
+ .vscode
10
+ .git
11
+ node_modules
12
+ env.yml
13
+ .prettierrc
14
+ .gitattributes
15
+ README.md
16
+ dungeons
17
+ schemas
@@ -4,28 +4,16 @@
4
4
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
5
  "version": "0.2.0",
6
6
  "configurations": [
7
- {
8
- "command": "npm run simple",
9
- "name": "simple",
10
- "request": "launch",
11
- "type": "node-terminal"
12
- },
13
- {
14
- "command": "npm run complex",
15
- "name": "complex",
16
- "request": "launch",
17
- "type": "node-terminal"
18
- },
19
7
  {
20
8
  "command": "npm run dev",
21
- "name": "dev",
9
+ "name": "scratch",
22
10
  "request": "launch",
23
11
  "type": "node-terminal"
24
12
  },
25
13
  {
26
14
  "type": "node",
27
15
  "request": "launch",
28
- "name": "go",
16
+ "name": "run dungeon",
29
17
  "runtimeExecutable": "nodemon",
30
18
  "runtimeArgs": ["--inspect"],
31
19
  "program": "${workspaceFolder}/index.js",
@@ -34,7 +22,30 @@
34
22
  "console": "integratedTerminal",
35
23
  "internalConsoleOptions": "neverOpen",
36
24
  "skipFiles": ["<node_internals>/**"],
25
+ "preLaunchTask": "npm: prune",
37
26
  },
27
+ {
28
+ "type": "node",
29
+ "request": "launch",
30
+ "name": "current file",
31
+ "runtimeExecutable": "nodemon",
32
+ "program": "${file}",
33
+ "restart": true,
34
+ "console": "integratedTerminal",
35
+ "internalConsoleOptions": "neverOpen",
36
+ "env": {
37
+ "NODE_ENV": "dev"
38
+ }
39
+ },
40
+ {
41
+ "command": "npm run func:local",
42
+ "name": "cloud local",
43
+ "request": "launch",
44
+ "type": "node-terminal",
45
+ "env": {
46
+ "NODE_ENV": "dev"
47
+ }
48
+ },
38
49
  {
39
50
  "type": "node",
40
51
  "request": "launch",
@@ -47,6 +58,18 @@
47
58
  "console": "integratedTerminal",
48
59
  "internalConsoleOptions": "neverOpen",
49
60
  "skipFiles": ["<node_internals>/**"],
61
+ },
62
+ {
63
+ "type": "node-terminal",
64
+ "request": "launch",
65
+ "name": "simple",
66
+ "command": "node ${workspaceFolder}/index.js --simple"
67
+ },
68
+ {
69
+ "type": "node-terminal",
70
+ "request": "launch",
71
+ "name": "complex",
72
+ "command": "node ${workspaceFolder}/index.js --complex"
50
73
  }
51
74
  ]
52
75
  }
@@ -11,6 +11,7 @@
11
11
  "Dont",
12
12
  "durtle",
13
13
  "fonk",
14
+ "gameplay",
14
15
  "garply",
15
16
  "jackalope",
16
17
  "linny",
@@ -23,6 +24,7 @@
23
24
  "nessie",
24
25
  "nesty",
25
26
  "planthopper",
27
+ "PROOMPTY",
26
28
  "psyllid",
27
29
  "tatzelwurm",
28
30
  "timesoup",
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "npm: prune",
6
+ "type": "npm",
7
+ "script": "prune",
8
+ "group": "build",
9
+ "problemMatcher": []
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,93 @@
1
+ const { GoogleGenerativeAI } = require("@google/generative-ai");
2
+ const u = require("ak-tools");
3
+
4
+
5
+ const dotenv = require("dotenv");
6
+ dotenv.config();
7
+
8
+ const { GEMINI_API_KEY: API_KEY, NODE_ENV = "unknown" } = process.env;
9
+ if (!API_KEY) throw new Error("Please provide a Gemini API key");
10
+
11
+ async function generateSchema(userInput) {
12
+ const gemini = new GoogleGenerativeAI(API_KEY);
13
+ const model = gemini.getGenerativeModel({ model: "gemini-1.5-flash" });
14
+ const PROOMPTY = await u.load("./components/prompt.txt");
15
+ const prompt = `
16
+ Given the following information about a website or app:
17
+
18
+ ${userInput}
19
+
20
+ ${PROOMPTY}
21
+
22
+ REMEMBER, YOUR INPUT IS:
23
+
24
+ ${userInput}
25
+ `.trim();
26
+
27
+ let schema;
28
+ let schemaIsValid = false;
29
+ let attempts = 0;
30
+ do {
31
+ attempts++;
32
+ const result = await model.generateContent(prompt);
33
+ const response = await result.response;
34
+ const text = response.text();
35
+ schema = processResponse(text);
36
+ schemaIsValid = validator(schema);
37
+ } while (!schemaIsValid);
38
+
39
+ return schema;
40
+ }
41
+
42
+ function processResponse(text) {
43
+ let json;
44
+ // check for ```json
45
+ const start = text.indexOf("```json");
46
+ const end = text.indexOf("```", start + 1);
47
+
48
+ if (start === -1 || end === -1) {
49
+ const start = text.indexOf("{");
50
+ const end = text.lastIndexOf("}");
51
+ json = text.slice(start, end + 1).trim();
52
+ }
53
+
54
+ json = text.slice(start + 7, end).trim();
55
+
56
+ try {
57
+ return JSON.parse(json);
58
+ }
59
+ catch (e) {
60
+ return null;
61
+ }
62
+
63
+
64
+ }
65
+
66
+ function validator(schema) {
67
+ let valid = true;
68
+
69
+ //null schema are always invalid
70
+ if (!schema) valid = false;
71
+
72
+ //must have 3 or more events
73
+ if (schema.events.length < 3) valid = false;
74
+
75
+ //must have 2 or more superProps
76
+ if (Object.keys(schema.superProps).length < 2) valid = false;
77
+
78
+ //must have 2 or more userProps
79
+ if (Object.keys(schema.userProps).length < 2) valid = false;
80
+
81
+ return valid;
82
+ }
83
+
84
+
85
+ if (require.main === module) {
86
+ generateSchema(`a dungeons and dragons inspired game where players can create characters, join parties, and go on quests and fight bosses!`)
87
+ .then((result) => {
88
+ if (NODE_ENV === "dev") debugger;
89
+ })
90
+ .catch((error) => {
91
+ if (NODE_ENV === "dev") debugger;
92
+ });
93
+ }
@@ -5,6 +5,8 @@ const dayjs = require('dayjs');
5
5
  const { openFinder } = require('./utils');
6
6
  const { existsSync } = fs;
7
7
  const path = require('path');
8
+ require('dotenv').config();
9
+ const { NODE_ENV = "unknown" } = process.env;
8
10
 
9
11
 
10
12
  let tempDir;
@@ -185,3 +187,15 @@ async function generateLineChart(rawData, signupEvents = ["sign up"], fileName)
185
187
  }
186
188
 
187
189
  module.exports = { generateLineChart };
190
+
191
+
192
+
193
+ if (require.main === module) {
194
+ generateLineChart()
195
+ .then((result)=>{
196
+ if (NODE_ENV === "dev") debugger;
197
+ })
198
+ .catch((error)=>{
199
+ if (NODE_ENV === "dev") debugger;
200
+ })
201
+ }
package/components/cli.js CHANGED
@@ -34,6 +34,12 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
34
34
  describe: 'project token; if supplied data will be sent to mixpanel',
35
35
  type: 'string'
36
36
  })
37
+ .option("batchSize", {
38
+ demandOption: false,
39
+ alias: 'b',
40
+ describe: 'batch size for chunking data',
41
+ type: 'number'
42
+ })
37
43
  .option("seed", {
38
44
  demandOption: false,
39
45
  alias: 's',
@@ -42,7 +48,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
42
48
  })
43
49
  .option("format", {
44
50
  demandOption: false,
45
- default: 'csv',
51
+ default: 'json',
46
52
  alias: 'f',
47
53
  describe: 'csv or json',
48
54
  type: 'string'
@@ -81,7 +87,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
81
87
  demandOption: false,
82
88
  default: 'US',
83
89
  alias: 'r',
84
- describe: 'either US or EU',
90
+ describe: 'either US or EU or IN',
85
91
  type: 'string'
86
92
  })
87
93
  .option('concurrency', {
@@ -2,6 +2,7 @@ require('dotenv').config();
2
2
  const akTools = require('ak-tools');
3
3
  const { rand, makeName } = akTools;
4
4
  let { OAUTH_TOKEN = "" } = process.env;
5
+ const { NODE_ENV = "unknown" } = process.env;
5
6
 
6
7
  /**
7
8
  * Main function to create a project and add group keys to it.
@@ -152,4 +153,14 @@ async function addGroupKeys(groupKeyDfns = [], projectId, oauthToken = OAUTH_TOK
152
153
  }
153
154
 
154
155
 
156
+ if (require.main === module) {
157
+ main()
158
+ .then((result)=>{
159
+ if (NODE_ENV === "dev") debugger;
160
+ })
161
+ .catch((error)=>{
162
+ if (NODE_ENV === "dev") debugger;
163
+ })
164
+ }
165
+
155
166
  module.exports = main;
@@ -0,0 +1,98 @@
1
+ Please generate a JSON schema for event analytics that strictly adheres to these TypeScript types:
2
+
3
+ ```typescript
4
+ type Primitives = string | number | boolean | Date | Record<string, any>;
5
+ type ValidValue = Primitives | Primitives[];
6
+
7
+ /**
8
+ * event schema describes a single event that we might track with any number of properties
9
+ */
10
+ interface EventSchema {
11
+ event: string;
12
+ weight: number;
13
+ properties?: Record<string, ValidValue>;
14
+ isFirstEvent?: boolean;
15
+ isChurnEvent?: boolean;
16
+ }
17
+
18
+ /**
19
+ * super props are shared across all events; common dimensions
20
+ */
21
+ type superProps = Record<string, ValidValue>;
22
+
23
+ /**
24
+ * user props describe a user independent of their actions; user dimensions
25
+ */
26
+ type userProps = Record<string, ValidValue>;
27
+
28
+ /**
29
+ * the schema YOU need to build
30
+ */
31
+ type Schema = {
32
+ events: EventSchema[];
33
+ superProps: superProps;
34
+ userProps: userProps;
35
+ };
36
+ ```
37
+
38
+
39
+ The schema that YOU make should include common events and properties, super properties, and user properties that describe TYPICAL user behavior for the website i mentioned above.
40
+
41
+ Let me give you a few examples, so you know how to respond.
42
+
43
+ **Example 1:**
44
+
45
+ Input: a generic e-commerce website thats sells a variety of products and also video streaming
46
+
47
+ Output:
48
+ ```json
49
+ {"events":[{"event":"checkout","weight":2,"properties":{"amount":[5,500],"currency":["USD","CAD","EUR","BTC","ETH","JPY"],"coupon":["none","10%OFF","20%OFF"],"numItems":[1,10]}},{"event":"add to cart","weight":4,"properties":{"amount":[5,500],"rating":[1,5],"reviews":[0,35],"isFeaturedItem":[true,false,false],"itemCategory":["electronics","clothing","accessories","home goods","toys","tools","books","music","movies"]}},{"event":"page view","weight":10,"properties":{"page":["/","/","/help","/account","/watch","/listen","/product","/people","/peace"]}},{"event":"watch video","weight":8,"properties":{"videoCategory":["humor","scary","amsr","music","documentary","educational"],"isFeaturedItem":[true,false,false],"watchTimeSec":[10,600],"quality":["2160p","1440p","1080p","720p","480p","360p","240p"],"format":["mp4","avi","mov","mpg"]}},{"event":"view item","weight":8,"properties":{"isFeaturedItem":[true,false,false],"itemCategory":["office","school","art","crafts","party","wedding","baby","kids","adults","seniors"],"rating":[1,5]}},{"event":"save item","weight":5,"properties":{"isFeaturedItem":[true,false,false],"itemCategory":["electronics","games","food","beverages","health","beauty","sports","outdoors","party","wedding","baby","kids","adults","seniors"]}},{"event":"rent item","weight":2,"properties":{"isFeaturedItem":[true,false,false],"itemCategory":["electronics","clothing","games","food","beauty","sports","outdoors","automotive","wedding","baby","seniors"]}},{"event":"sign up","isFirstEvent":true,"weight":0,"properties":{"variants":["A","B","C","Control"],"flows":["new","existing","loyal","churned"],"flags":["on","off"],"experiment_ids":["1234","5678","9012","3456","7890"],"multiVariate":[true,false]}}],"superProps":{"platform":["web","mobile","kiosk","smartTV"],"currentTheme":["light","dark","custom"]},"userProps":{"loyalty_points":[1,100000],"age":[18,70],"premium":[true,false],"language":["english","spanish","french"]}}
50
+ ```
51
+
52
+ **Example 2:**
53
+
54
+ Input: https://www.nytimes.com
55
+
56
+ Output:
57
+ ```json
58
+ {"events":[{"event":"page view","weight":10,"properties":{"page":["/","/section/world","/section/us","/section/politics","/section/business","/section/opinion","/section/technology","/section/science","/section/health","/section/sports","/section/arts","/section/fashion","/section/food","/section/travel"]}},{"event":"article read","weight":8,"properties":{"category":["world news","politics","business","technology","science","health","sports","arts","fashion","food","travel"],"length":["short (< 3 mins)","medium (3-5 mins)","long (> 5 mins)"],"author_reputation":["emerging","established","renowned"],"comment_interaction":["low (<10 comments)","medium (10-50 comments)","high (>50 comments)"]}},{"event":"search","weight":6,"properties":{"search_term_length":["short (1-3 words)","medium (4-6 words)","long (7+ words)"],"results_returned":["none","few (1-3 results)","some (4-10 results)","many (>10 results)"],"result_clicked":["first result","within top 5","none clicked"]}},{"event":"subscribe","weight":2,"properties":{"plan":["basic","premium","all-access"],"payment_method":["credit card","paypal","apple pay"],"trial_offer_accepted":[true,false]}},{"event":"newsletter sign up","weight":3,"properties":{"newsletter_type":["daily briefing","morning briefing","opinion","cooking","arts"],"opt_in_for_promotions":[true,false]}},{"event":"comment posted","weight":4,"properties":{"comment_length":["short (< 50 words)","medium (50-150 words)","long (> 150 words)"],"article_category":["news","opinion","review"],"visibility":["public","private","friends only"]}}],"superProps":{"platform":["web","mobile","tablet"],"loggedIn":[true,false],"time_of_day":["morning","afternoon","evening","night"],"browser_type":["Chrome","Firefox","Safari","Edge"]},"userProps":{"subscriptionType":["none","basic","premium","all-access"],"age_group":["<18","18-24","25-34","35-44","45-54","55-64","65+"],"location":["USA","Europe","Asia","Other"],"interests":["politics","technology","health","sports","arts","lifestyle","science","education"]}}
59
+ ```
60
+
61
+ **Example 3:**
62
+
63
+ Input: the love's truck stop app (love's connect: https://www.loves.com/en/my-love-rewards/download-loves-connect)
64
+
65
+ Output:
66
+ ```json
67
+ {"events":[{"event":"home button","weight":8,"properties":{"button type":["front","side","middle"]}},{"event":"route button","weight":6,"properties":{"from screen":["home","services"]}},{"event":"services button","weight":2,"properties":{"is emergency?":[false,true]}},{"event":"stores button","weight":3,"properties":{"is from push?":[true,false]}},{"event":"email link clicked","weight":1,"properties":{"campaign_name":["Pro Email Campaign 1","Pro Email Campaign 2","Pro Email Campaign 3"]}},{"event":"pay button","weight":4,"properties":{}},{"event":"pay with location","weight":3,"properties":{}},{"event":"payment select","weight":4,"properties":{}},{"event":"select pump","weight":5,"properties":{"pump":[1,32]}},{"event":"select fuel","weight":3,"properties":{"fuel type":["diesel","reefer","def"],"fuel price":[3.99,6.79]}},{"event":"fueling","weight":4,"properties":{"fuel volume":[20,150]}}],"superProps":{"campaign_name":["Pro Email Campaign 1","Pro Email Campaign 2","Pro Email Campaign 3"],"feature flags":[["first 100k miles"],["dark mode","first 100k miles"],["dark mode","free shower"],["free shower"]]},"userProps":{"driver type":["Leased","Owner","Fleet"],"marketing":["in-app","email","sms","call"],"member status":["base","gold","platinum","diamond"],"lifetime value":[0,10000]}}
68
+ ```
69
+
70
+ **Example 4:**
71
+
72
+ Input: the web app for one medical, a modern healthcare provider (https://app.onemedical.com/)
73
+
74
+ Output:
75
+ ```json
76
+ {"events":[{"event":"login","weight":9,"properties":{"method":["standard email/password","google","apple"],"success":[true,false],"error_type":["none","password incorrect","user not found","account locked"]}},{"event":"appointment booking","weight":8,"properties":{"type":["physical","virtual"],"specialty":["primary care","mental health","pediatrics","nutrition","allergy","dermatology"],"duration":["15 minutes","30 minutes","1 hour"],"booking_advance_days":["same day","1-3 days","4-7 days","more than a week"]}},{"event":"profile update","weight":4,"properties":{"section_updated":["personal information","insurance details","medical history","preferences"],"fields_changed":["1-2","3-5","more than 5"]}},{"event":"message to provider","weight":6,"properties":{"message_length":["short (<50 words)","medium (50-150 words)","long (>150 words)"],"response_time":["<1 hour","1-24 hours","1-2 days","no response"],"urgency":["low","medium","high"]}},{"event":"medication refill request","weight":5,"properties":{"medication":["prescription","over-the-counter"],"quantity":["1 month","3 months","6 months"],"auto_refill_enrolled":[true,false]}},{"event":"health record access","weight":7,"properties":{"record_type":["lab results","vaccination records","prescription history","doctor notes"],"access_device":["desktop","mobile"],"time_spent":["<1 min","1-5 mins","5+ mins"]}},{"event":"feedback submission","weight":3,"properties":{"feedback_type":["service","app usability","provider"],"rating":["1","2","3","4","5"],"comment_provided":[true,false]}},{"event":"notification settings update","weight":2,"properties":{"notifications_enabled":[true,false],"types_enabled":["appointment reminders","health updates","newsletters","promotional offers"]}}],"superProps":{"platform":["web","mobile"],"loggedIn":[true,false],"user_type":["patient","provider","admin"],"subscription_level":["free","standard","premium"]},"userProps":{"membership_status":["active","lapsed","pending renewal"],"age_group":["<18","18-24","25-34","35-44","45-54","55-64","65+"],"has_chronic_conditions":[true,false],"preferred_contact_method":["email","phone","sms"]}}
77
+ ```
78
+
79
+ **Example 5:**
80
+
81
+ Input: the web app for coinbase, a cryptocurrency exchange (https://www.coinbase.com/)
82
+
83
+ Output:
84
+ ```json
85
+ {"events":[{"event":"login","weight":9,"properties":{"method":["standard email/password","two-factor authentication","biometric"],"success":[true,false],"error_type":["none","password incorrect","user not found","account locked","2FA failed"]}},{"event":"trade executed","weight":8,"properties":{"crypto_pair":["BTC/USD","ETH/USD","LTC/USD","XRP/USD","BCH/USD"],"trade_type":["buy","sell"],"order_type":["market","limit","stop loss","stop limit"],"quantity":["<1","1-5","5-10","10+"],"trade_successful":[true,false],"error_type":["none","insufficient funds","slippage too high","network error"]}},{"event":"account funding","weight":7,"properties":{"method":["bank transfer","credit card","crypto deposit"],"currency":["USD","EUR","GBP"],"amount":["<100","100-1000","1000-5000","5000+"],"successful":[true,false],"error_type":["none","transaction declined","limits exceeded","network error"]}},{"event":"withdrawal request","weight":6,"properties":{"method":["bank transfer","crypto withdrawal"],"currency":["USD","BTC","ETH"],"amount":["<100","100-1000","1000-5000","5000+"],"successful":[true,false],"error_type":["none","insufficient funds","limits exceeded","network error"]}},{"event":"profile update","weight":3,"properties":{"fields_updated":["email","phone","two-factor authentication settings","payment methods"],"successful":[true,false]}},{"event":"security alert","weight":4,"properties":{"alert_type":["login from new device","suspicious transaction","profile changes"],"response_action":["no action","contact support","reset password","enable 2FA"]}},{"event":"price alert set","weight":2,"properties":{"crypto":["BTC","ETH","LTC","XRP"],"threshold":["<5000","5000-10000","10000-20000","20000+"],"alert_type":["price increase","price decrease"]}},{"event":"customer support interaction","weight":5,"properties":{"issue_type":["account access","trade issue","withdrawal problem","other"],"contact_method":["email","phone","live chat"],"resolution":["resolved","unresolved","escalated"]}}],"superProps":{"platform":["web","iOS app","Android app"],"loggedIn":[true,false],"verification_level":["none","basic","verified","pro"]},"userProps":{"account_type":["basic","pro"],"trading_frequency":["daily","weekly","monthly","rarely"],"preferred_crypto":["BTC","ETH","LTC","XRP"],"region":["North America","Europe","Asia","Other"],"investment_level":["<1000","1000-5000","5000-10000","10000+"]}}
86
+ ```
87
+
88
+ **ADDITIONAL GUIDELINES:**
89
+
90
+ * Use specific and descriptive event names.
91
+ * DO NOT declare property types, properties are the key names and include an array of POSSIBLE values which could be chosen for the event or user
92
+ * the properties within each event should adhere to the defined TypeScript types (Primitives and ValidValue)
93
+ * For numerical ranges, you can use [min, max] in the JSON as above
94
+ * Consider both super properties (applied to all events) and user properties; they should be different from event props
95
+ * DO NOT include user IDs, emails, or location properties anywhere data in the schema; ONLY highlight the properties specific to the website or app
96
+ * DO NOT be lazy an say ['array of payment types'] or ['array of product categories'] - be specific ... ['USD', 'CAD', 'EUR', 'BTC', 'ETH', 'JPY'] ... we want to see the actual values that would be passed live production data
97
+ * event names should be lower case by convention; prefer spaces to underscores so "add to cart" instead of "add_to_cart"
98
+ * you should ALWAYS output valid JSON as your output will be fed into a JSON schema validator
@@ -12,7 +12,7 @@ dayjs.extend(utc);
12
12
  require('dotenv').config();
13
13
  const { domainSuffix, domainPrefix } = require('./defaults');
14
14
 
15
- /** @typedef {import('../types').Config} Config */
15
+ /** @typedef {import('../types').Dungeon} Config */
16
16
  /** @typedef {import('../types').EventConfig} EventConfig */
17
17
  /** @typedef {import('../types').ValueValid} ValueValid */
18
18
  /** @typedef {import('../types').HookedArray} hookArray */
@@ -179,14 +179,26 @@ function day(start, end) {
179
179
  * @param {ValueValid} value
180
180
  */
181
181
  function choose(value) {
182
+ // let wasFunctionCalled = false;
182
183
  const chance = getChance();
184
+
183
185
  try {
184
186
  // Keep resolving the value if it's a function
185
187
  while (typeof value === 'function') {
186
188
  value = value();
189
+ // wasFunctionCalled = true;
187
190
  }
188
191
 
192
+ // allow functions which create arrays of objects to pass through
193
+ // if (Array.isArray(value) && wasFunctionCalled && value.length > 1 && value[0] === null) {
194
+ // return value.slice(1);
195
+ // }
196
+
189
197
  // Now, if the resolved value is an array, use chance.pickone
198
+ if (Array.isArray(value) && hasSameKeys(value)) {
199
+ return value;
200
+ }
201
+
190
202
  if (Array.isArray(value)) {
191
203
  return chance.pickone(value);
192
204
  }
@@ -204,10 +216,36 @@ function choose(value) {
204
216
  }
205
217
  catch (e) {
206
218
  console.error(`\n\nerror on value: ${value};\n\n`, e, '\n\n');
219
+ throw e;
207
220
  return '';
208
221
  }
209
222
  }
210
223
 
224
+
225
+ function hasSameKeys(arr) {
226
+ if (arr.length <= 1) {
227
+ return true; // An empty array or an array with one object always has the same keys
228
+ }
229
+
230
+ const firstKeys = Object.keys(arr[0]);
231
+
232
+ for (let i = 1; i < arr.length; i++) {
233
+ const currentKeys = Object.keys(arr[i]);
234
+
235
+ if (currentKeys.length !== firstKeys.length) {
236
+ return false; // Different number of keys
237
+ }
238
+
239
+ for (const key of firstKeys) {
240
+ if (!currentKeys.includes(key)) {
241
+ return false; // Key missing in current object
242
+ }
243
+ }
244
+ }
245
+
246
+ return true; // All objects have the same keys
247
+ }
248
+
211
249
  /**
212
250
  * keeps picking from an array until the array is exhausted
213
251
  * @param {Array} arr
@@ -316,6 +354,58 @@ function range(a, b, step = 1) {
316
354
  };
317
355
 
318
356
 
357
+ function companyName(words = 2, separator = " ") {
358
+ const industryAdjectives = ["advanced", "premier", "integrated", "optimized", "comprehensive", "expert",
359
+ "visionary", "progressive", "transformative", "pioneering", "streamlined",
360
+ "cutting-edge", "impactful", "purpose-driven", "value-oriented", "future-ready",
361
+ "scalable", "responsive", "data-driven", "cloud-based", "user-friendly",
362
+ "high-performance", "secure", "compliant", "ethical", "inclusive",
363
+ "transparent", "community-focused", "environmentally-conscious", "socially-responsible", "innovative", "dynamic", "global", "leading", "reliable", "trusted",
364
+ "strategic", "efficient", "sustainable", "creative", "agile", "resilient",
365
+ "collaborative", "customer-centric", "forward-thinking", "results-driven", "gizmo", "contraption", "doodle", "whimsy", "quirk", "spark", "zing",
366
+ "zap", "pop", "fizz", "whirl", "twirl", "swirl", "jumble", "tumble",
367
+ "hodgepodge", "mishmash", "kaleidoscope", "labyrinth", "maze", "puzzle",
368
+ "enigma", "conundrum", "paradox", "oxymoron", "chimera", "centaur",
369
+ "griffin", "phoenix", "unicorn", "dragon", "mermaid", "yeti", "bigfoot",
370
+ "loch ness monster", "chupacabra", "kraken", "leviathan", "behemoth",
371
+ "juggernaut", "goliath", "david", "odyssey", "pilgrimage", "crusade",
372
+ "quest", "adventure", "escapade", "frolic", "romp", "lark", "spree",
373
+ "binge", "jag", "bender", "tear", "rampage", "riot", "ruckus", "rumpus",
374
+ "hullabaloo", "brouhaha", "kerfuffle", "shindig", "hootenanny", "jamboree",
375
+ "fiesta", "carnival", "gala", "soiree", "bash", "fete", "jubilee"
376
+
377
+ ];
378
+
379
+ const companyNouns = [
380
+ "solutions", "group", "partners", "ventures", "holdings", "enterprises",
381
+ "systems", "technologies", "innovations", "associates", "corporation", "inc.",
382
+ "ltd.", "plc.", "gmbh", "s.a.", "llc.", "network", "alliance", "consortium", "collective", "foundation", "institute",
383
+ "laboratory", "agency", "bureau", "department", "division", "branch",
384
+ "office", "center", "hub", "platform", "ecosystem", "marketplace",
385
+ "exchange", "clearinghouse", "repository", "archive", "registry",
386
+ "database", "framework", "infrastructure", "architecture", "protocol",
387
+ "standard", "specification", "guideline", "blueprint", "roadmap",
388
+ "strategy", "plan", "initiative", "program", "project", "campaign",
389
+ "operation", "mission", "task", "force", "team", "crew", "squad",
390
+ "unit", "cell", "pod", "cohort", "community", "network", "circle",
391
+ "forum", "council", "board", "committee", "panel", "jury", "tribunal"
392
+ ];
393
+
394
+ let name = "";
395
+ const cycle = [industryAdjectives, companyNouns];
396
+ for (let i = 0; i < words; i++) {
397
+ const index = i % cycle.length;
398
+ const word = cycle[index][Math.floor(Math.random() * cycle[index].length)];
399
+ if (name === "") {
400
+ name = word;
401
+ } else {
402
+ name += separator + word;
403
+ }
404
+ }
405
+
406
+ return name;
407
+ }
408
+
319
409
 
320
410
  /*
321
411
  ----
@@ -877,6 +967,8 @@ function person(userId, bornDaysAgo = 30, isAnonymous = false, hasAvatar = false
877
967
  let avPath = gender === 'male' ? `/men/${randomAvatarNumber}.jpg` : `/women/${randomAvatarNumber}.jpg`;
878
968
  let avatar = avatarPrefix + avPath;
879
969
  let created = dayjs().subtract(bornDaysAgo, 'day').format('YYYY-MM-DD');
970
+
971
+
880
972
  // const created = date(bornDaysAgo, true)();
881
973
 
882
974
 
@@ -925,7 +1017,35 @@ function person(userId, bornDaysAgo = 30, isAnonymous = false, hasAvatar = false
925
1017
  };
926
1018
 
927
1019
 
928
-
1020
+ function wrapFunc(obj, func, recursion = 0, parentKey = null, grandParentKey = null, whitelist = [
1021
+ "events",
1022
+ "superProps",
1023
+ "userProps",
1024
+ "scdProps",
1025
+ "mirrorProps",
1026
+ "groupEvents",
1027
+ "groupProps"
1028
+ ]) {
1029
+ if (recursion === 0) {
1030
+ // Only process top-level keys in the whitelist
1031
+ for (const key in obj) {
1032
+ if (whitelist.includes(key)) {
1033
+ obj[key] = wrapFunc(obj[key], func, recursion + 1, key, null, whitelist);
1034
+ }
1035
+ }
1036
+ } else {
1037
+ if (Array.isArray(obj) && grandParentKey === 'properties') {
1038
+ return func(obj);
1039
+ } else if (typeof obj === 'object' && obj !== null) {
1040
+ for (const key in obj) {
1041
+ if (obj.hasOwnProperty(key)) {
1042
+ obj[key] = wrapFunc(obj[key], func, recursion + 1, key, parentKey, whitelist);
1043
+ }
1044
+ }
1045
+ }
1046
+ }
1047
+ return obj;
1048
+ }
929
1049
 
930
1050
  //UNUSED
931
1051
 
@@ -989,9 +1109,9 @@ module.exports = {
989
1109
  exhaust,
990
1110
  integer,
991
1111
  TimeSoup,
992
-
1112
+ companyName,
993
1113
  generateEmoji,
994
-
1114
+ haveSameKeys: hasSameKeys,
995
1115
 
996
1116
  initChance,
997
1117
  getChance,
@@ -1025,5 +1145,6 @@ module.exports = {
1025
1145
  streamJSON,
1026
1146
  streamCSV,
1027
1147
  datesBetween,
1028
- weighChoices
1148
+ weighChoices,
1149
+ wrapFunc,
1029
1150
  };
@@ -16,7 +16,7 @@ dayjs.extend(utc);
16
16
  const { uid, comma } = require('ak-tools');
17
17
  const { pickAWinner, weighNumRange, date, integer } = require('../components/utils');
18
18
 
19
- /** @type {import('../types').Config} */
19
+ /** @type {import('../types').Dungeon} */
20
20
  const config = {
21
21
  token: "",
22
22
  seed: "foo bar",
@@ -18,7 +18,7 @@ const { pickAWinner, weighNumRange, date, integer } = require('../components/uti
18
18
 
19
19
 
20
20
 
21
- /** @type {import('../types').Config} */
21
+ /** @type {import('../types').Dungeon} */
22
22
  const config = {
23
23
  token: "",
24
24
  seed: "foo bar",
@@ -18,7 +18,7 @@ dayjs.extend(utc);
18
18
  const { uid, comma } = require('ak-tools');
19
19
  const { pickAWinner, weighNumRange, date, integer } = require('../components/utils');
20
20
 
21
- /** @type {import('../types').Config} */
21
+ /** @type {import('../types').Dungeon} */
22
22
  const config = {
23
23
  token: "",
24
24
  seed: "lets go",
@@ -20,7 +20,7 @@ const EVENTS = 50_000
20
20
  const USERS = EVENTS / 100
21
21
 
22
22
 
23
- /** @type {import('../types.js').Config} */
23
+ /** @type {import('../types.js').Dungeon} */
24
24
  const config = {
25
25
  token: "",
26
26
  seed: "it's business time...",