make-mp-data 1.3.3 โ†’ 1.3.4

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.
@@ -23,5 +23,5 @@
23
23
  ],
24
24
  "jest.runMode": "on-demand",
25
25
  "jest.jestCommandLine": "npm run test --",
26
- "js/ts.implicitProjectConfig.checkJs": false,
26
+ "js/ts.implicitProjectConfig.checkJs": true,
27
27
  }
package/cli.js CHANGED
@@ -15,6 +15,7 @@ by ak@mixpanel.com
15
15
 
16
16
  function cliParams() {
17
17
  console.log(hero);
18
+ // @ts-ignore
18
19
  const args = yargs(process.argv.splice(2))
19
20
  .scriptName("make-mp-data")
20
21
  .usage(`\nusage:\nnpx $0 [dataModel.js] [options]
@@ -78,17 +79,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
78
79
  describe: 'use complex data model (model all entities)',
79
80
  alias: 'c',
80
81
  type: 'boolean',
81
- coerce: (value) => {
82
- if (typeof value === 'boolean') return value;
83
- if (value === 'true') {
84
- return true;
85
- }
86
- if (value === 'false') {
87
- return false;
88
- }
89
-
90
- return true;
91
- }
82
+ coerce: boolCoerce
92
83
  })
93
84
  .option("writeToDisk", {
94
85
  demandOption: false,
@@ -96,13 +87,25 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
96
87
  describe: 'write data to disk',
97
88
  alias: 'w',
98
89
  type: 'boolean',
99
- coerce: (value) => {
100
- if (typeof value === 'string') {
101
- return value.toLowerCase() === 'true';
102
- }
103
- return value;
104
- }
90
+ coerce: boolCoerce
91
+ })
92
+ .option("sessionIds", {
93
+ demandOption: false,
94
+ default: false,
95
+ describe: 'create session ids in the data',
96
+ alias: 'sid',
97
+ type: 'boolean',
98
+ coerce: boolCoerce
99
+ })
100
+ .option("anonIds", {
101
+ demandOption: false,
102
+ default: false,
103
+ describe: 'create anonymous ids in the data',
104
+ alias: 'aid',
105
+ type: 'boolean',
106
+ coerce: boolCoerce
105
107
  })
108
+
106
109
  .help()
107
110
  .wrap(null)
108
111
  .argv;
@@ -115,4 +118,14 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
115
118
  }
116
119
 
117
120
 
121
+ function boolCoerce(value, foo) {
122
+ if (typeof value === 'boolean') return value;
123
+ if (typeof value === 'string') {
124
+ return value.toLowerCase() === 'true';
125
+ }
126
+ return value;
127
+
128
+ }
129
+
130
+
118
131
  module.exports = cliParams;
package/index.js CHANGED
@@ -11,15 +11,13 @@ const mp = require("mixpanel-import");
11
11
  const path = require("path");
12
12
  const Chance = require("chance");
13
13
  const chance = new Chance();
14
- const { touch, comma, bytesHuman, mkdir } = require("ak-tools");
15
- const Papa = require("papaparse");
14
+ const { comma, bytesHuman, mkdir, makeName, md5, clone, tracker, uid } = require("ak-tools");
16
15
  const u = require("./utils.js");
17
16
  const AKsTimeSoup = require("./timesoup.js");
18
17
  const dayjs = require("dayjs");
19
18
  const utc = require("dayjs/plugin/utc");
20
19
  dayjs.extend(utc);
21
20
  const cliParams = require("./cli.js");
22
- const { makeName, md5, clone, tracker, uid } = require('ak-tools');
23
21
  const NOW = dayjs().unix();
24
22
  let VERBOSE = false;
25
23
  let isCLI = false;
@@ -88,7 +86,7 @@ async function main(config) {
88
86
  });
89
87
  log(`------------------SETUP------------------`);
90
88
  log(`\nyour data simulation will heretofore be known as: \n\n\t${simulationName.toUpperCase()}...\n`);
91
- log(`and your configuration is:\n\n`, JSON.stringify({ seed, numEvents, numUsers, numDays, format, token, region, writeToDisk }, null, 2));
89
+ log(`and your configuration is:\n\n`, JSON.stringify({ seed, numEvents, numUsers, numDays, format, token, region, writeToDisk, anonIds, sessionIds }, null, 2));
92
90
  log(`------------------SETUP------------------`, "\n");
93
91
 
94
92
 
@@ -116,11 +114,13 @@ async function main(config) {
116
114
  const weight = event.weight || 1;
117
115
  for (let i = 0; i < weight; i++) {
118
116
 
117
+ // @ts-ignore
119
118
  acc.push(event);
120
119
  }
121
120
  return acc;
122
121
  }, [])
123
122
 
123
+ // @ts-ignore
124
124
  .filter((e) => !e.isFirstEvent);
125
125
 
126
126
  const firstEvents = events.filter((e) => e.isFirstEvent);
@@ -276,7 +276,8 @@ async function main(config) {
276
276
  };
277
277
  }
278
278
  log(`-----------------WRITES------------------`, `\n\n`);
279
- //write the files
279
+
280
+ let writeFilePromises = [];
280
281
  if (writeToDisk) {
281
282
  if (verbose) log(`writing files... for ${simulationName}`);
282
283
  loopFiles: for (const ENTITY of pairs) {
@@ -295,25 +296,16 @@ async function main(config) {
295
296
  log(`\twriting ${path}`);
296
297
  //if it's a lookup table, it's always a CSV
297
298
  if (format === "csv" || path.includes("-LOOKUP.csv")) {
298
- const columns = u.getUniqueKeys(TABLE);
299
- //papa parse needs eac nested field JSON stringified
300
- TABLE.forEach((e) => {
301
- for (const key in e) {
302
- if (typeof e[key] === "object") e[key] = JSON.stringify(e[key]);
303
- }
304
- });
305
-
306
- const csv = Papa.unparse(TABLE, { columns });
307
- await touch(path, csv);
299
+ writeFilePromises.push(u.streamCSV(path, TABLE));
308
300
  }
309
301
  else {
310
- const ndjson = TABLE.map((d) => JSON.stringify(d)).join("\n");
311
- await touch(path, ndjson, false);
302
+ writeFilePromises.push(u.streamJSON(path, TABLE));
312
303
  }
313
304
 
314
305
  }
315
306
  }
316
307
  }
308
+ const fileWriteResults = await Promise.all(writeFilePromises);
317
309
 
318
310
  const importResults = { events: {}, users: {}, groups: [] };
319
311
 
@@ -402,6 +394,10 @@ function makeProfile(props, defaults) {
402
394
  ...defaults,
403
395
  };
404
396
 
397
+ // anonymous and session ids
398
+ if (!global.MP_SIMULATION_CONFIG?.anonIds) delete profile.anonymousIds;
399
+ if (!global.MP_SIMULATION_CONFIG?.sessionIds) delete profile.sessionIds;
400
+
405
401
  for (const key in props) {
406
402
  try {
407
403
  profile[key] = u.choose(props[key]);
@@ -413,7 +409,7 @@ function makeProfile(props, defaults) {
413
409
  return profile;
414
410
  }
415
411
  /**
416
- * @param {import('./types.d.ts').valueValid} prop
412
+ * @param {import('./types.d.ts').ValueValid} prop
417
413
  * @param {string} scdKey
418
414
  * @param {string} distinct_id
419
415
  * @param {number} mutations
@@ -496,8 +492,11 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
496
492
  for (const groupPair of groupKeys) {
497
493
  const groupKey = groupPair[0];
498
494
  const groupCardinality = groupPair[1];
495
+ const groupEvents = groupPair[2] || [];
499
496
 
500
- event[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
497
+ // empty array for group events means all events
498
+ if (!groupEvents.length) event[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
499
+ if (groupEvents.includes(event.event)) event[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
501
500
  }
502
501
 
503
502
  //make $insert_id
@@ -507,18 +506,21 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
507
506
  }
508
507
 
509
508
  function buildFileNames(config) {
510
- const { format = "csv", groupKeys = [], lookupTables = [] } = config;
511
- const extension = format === "csv" ? "csv" : "json";
509
+ const { format = "csv", groupKeys = [], lookupTables = [], m } = config;
510
+ let extension = "";
511
+ extension = format === "csv" ? "csv" : "json";
512
512
  // const current = dayjs.utc().format("MM-DD-HH");
513
513
  const simName = config.simulationName;
514
514
  let writeDir = "./";
515
515
  if (config.writeToDisk) writeDir = mkdir("./data");
516
+ if (typeof writeDir !== "string") throw new Error("writeDir must be a string");
517
+ if (typeof simName !== "string") throw new Error("simName must be a string");
516
518
 
517
519
  const writePaths = {
518
520
  eventFiles: [path.join(writeDir, `${simName}-EVENTS.${extension}`)],
519
521
  userFiles: [path.join(writeDir, `${simName}-USERS.${extension}`)],
520
522
  scdFiles: [],
521
- mirrorFiles: [path.join(writeDir, `${simName}-EVENTS-FUTURE-MIRROR.${extension}`)],
523
+ mirrorFiles: [],
522
524
  groupFiles: [],
523
525
  lookupFiles: [],
524
526
  folder: writeDir,
@@ -532,13 +534,16 @@ function buildFileNames(config) {
532
534
  );
533
535
  }
534
536
 
537
+ //add group files
535
538
  for (const groupPair of groupKeys) {
536
539
  const groupKey = groupPair[0];
540
+
537
541
  writePaths.groupFiles.push(
538
542
  path.join(writeDir, `${simName}-${groupKey}-GROUP.${extension}`)
539
543
  );
540
544
  }
541
545
 
546
+ //add lookup files
542
547
  for (const lookupTable of lookupTables) {
543
548
  const { key } = lookupTable;
544
549
  writePaths.lookupFiles.push(
@@ -547,10 +552,25 @@ function buildFileNames(config) {
547
552
  );
548
553
  }
549
554
 
555
+ //add mirror files
556
+ const mirrorProps = config?.mirrorProps || {};
557
+ if (Object.keys(mirrorProps).length) {
558
+ writePaths.mirrorFiles.push(
559
+ path.join(writeDir, `${simName}-MIRROR.${extension}`)
560
+ );
561
+ }
562
+
550
563
  return writePaths;
551
564
  }
552
565
 
566
+ /** @typedef {import('./types').EnrichedArray} EnrichArray */
567
+ /** @typedef {import('./types').EnrichArrayOptions} EnrichArrayOptions */
553
568
 
569
+ /**
570
+ * @param {any[]} arr
571
+ * @param {EnrichArrayOptions} opts
572
+ * @returns {EnrichArray}}
573
+ */
554
574
  function enrichArray(arr = [], opts = {}) {
555
575
  const { hook = a => a, type = "", ...rest } = opts;
556
576
 
@@ -558,9 +578,15 @@ function enrichArray(arr = [], opts = {}) {
558
578
  return arr.push(hook(item, type, rest));
559
579
  }
560
580
 
561
- arr.hPush = transformThenPush;
581
+ /** @type {EnrichArray} */
582
+ // @ts-ignore
583
+ const enrichedArray = arr;
584
+
585
+
586
+ enrichedArray.hPush = transformThenPush;
587
+
562
588
 
563
- return arr;
589
+ return enrichedArray;
564
590
  };
565
591
 
566
592
 
@@ -569,7 +595,9 @@ function enrichArray(arr = [], opts = {}) {
569
595
  if (require.main === module) {
570
596
  isCLI = true;
571
597
  const args = cliParams();
572
- const { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false } = args;
598
+ // @ts-ignore
599
+ const { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false, sessionIds, anonIds } = args;
600
+ // @ts-ignore
573
601
  const suppliedConfig = args._[0];
574
602
 
575
603
  //if the user specifics an separate config file
@@ -603,6 +631,8 @@ if (require.main === module) {
603
631
  if (region) config.region = region;
604
632
  if (writeToDisk) config.writeToDisk = writeToDisk;
605
633
  if (writeToDisk === 'false') config.writeToDisk = false;
634
+ if (sessionIds) config.sessionIds = sessionIds;
635
+ if (anonIds) config.anonIds = anonIds;
606
636
  config.verbose = true;
607
637
 
608
638
  main(config)
package/models/complex.js CHANGED
@@ -63,6 +63,27 @@ const config = {
63
63
 
64
64
  }
65
65
  },
66
+ {
67
+ "event": "comment",
68
+ "weight": 2,
69
+ "properties": {
70
+ length: weightedRange(1, 500, 1000, .25),
71
+ video_id: weightedRange(1, 50000, 420000, 1.4),
72
+ has_replies: [true, false, false, false, false],
73
+ has_photo: [true, false, false, false, false],
74
+
75
+ }
76
+ },
77
+ {
78
+ "event": "save video",
79
+ "weight": 4,
80
+ "properties": {
81
+ video_id: weightedRange(1, 50000, 420000, 1.4),
82
+ ui_control: ["toolbar", "menu", "keyboard"]
83
+
84
+
85
+ }
86
+ },
66
87
  {
67
88
  "event": "view item",
68
89
  "weight": 8,
@@ -143,8 +164,8 @@ const config = {
143
164
  each pair represents a group_key and the number of profiles for that key
144
165
  */
145
166
  groupKeys: [
146
- ['company_id', 500],
147
- ['room_id', 10000],
167
+ ['company_id', 500, []],
168
+ ['room_id', 10000, ["save video", "comment", "watch video"]],
148
169
 
149
170
  ],
150
171
  groupProps: {
@@ -192,7 +213,6 @@ const config = {
192
213
  copyright: ["all rights reserved", "creative commons", "creative commons", "public domain", "fair use"],
193
214
  uploader_id: chance.guid.bind(chance),
194
215
  "uploader influence": ["low", "low", "low", "medium", "medium", "high"],
195
- rating: weightedRange(1, 5),
196
216
  thumbs: weightedRange(0, 35),
197
217
  rating: ["G", "PG", "PG-13", "R", "NC-17", "PG-13", "R", "NC-17", "R", "PG", "PG"]
198
218
  }
@@ -286,7 +306,7 @@ function deviceAttributes(isMobile = false) {
286
306
  let devices = ["desktop", "laptop", "desktop", "laptop", "desktop", "laptop", "other"];
287
307
  if (isMobile) devices = [...devices, "mobile", "mobile", "mobile", "tablet"];
288
308
  const device = chance.pickone(devices);
289
- const oses = ["Windows", "macOS", "Windows", "macOS", "macOS", "Linux", "Windows", "macOS", "Windows", "macOS", "macOS", "TempleOS"];
309
+ let oses = ["Windows", "macOS", "Windows", "macOS", "macOS", "Linux", "Windows", "macOS", "Windows", "macOS", "macOS", "TempleOS"];
290
310
  if (isMobile) oses = [...oses, "iOS", "Android", "iOS", "Android"];
291
311
  const os = chance.pickone(oses);
292
312
  const browser = chance.pickone(["Chrome", "Firefox", "Safari", "Edge", "Opera", "IE", "Brave", "Vivaldi"]);
@@ -0,0 +1,110 @@
1
+ /**
2
+ * This is the default configuration file for the data generator in SIMPLE mode
3
+ * notice how the config object is structured, and see it's type definition in ./types.d.ts
4
+ * feel free to modify this file to customize the data you generate
5
+ * see helper functions in utils.js for more ways to generate data
6
+ */
7
+
8
+
9
+
10
+
11
+ const Chance = require('chance');
12
+ const chance = new Chance();
13
+ const dayjs = require("dayjs");
14
+ const utc = require("dayjs/plugin/utc");
15
+ dayjs.extend(utc);
16
+ const { uid, comma } = require('ak-tools');
17
+ const { weighList, weightedRange, date, integer } = require('../utils');
18
+
19
+ const itemCategories = ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"];
20
+
21
+ const videoCategories = ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"];
22
+
23
+ /** @type {import('../types').Config} */
24
+ const config = {
25
+ token: "",
26
+ seed: "foo bar",
27
+ numDays: 365, //how many days worth of data
28
+ numEvents: 10000000, //how many events
29
+ numUsers: 25000, //how many users
30
+ format: 'json', //csv or json
31
+ region: "US",
32
+ anonIds: true, //if true, anonymousIds are created for each user
33
+ sessionIds: false, //if true, sessionIds are created for each user
34
+
35
+ events: [
36
+ {
37
+ event: "foo",
38
+ weight: 10,
39
+ properties: {}
40
+ },
41
+ {
42
+ event: "bar",
43
+ weight: 9,
44
+ properties: {}
45
+ },
46
+ {
47
+ event: "baz",
48
+ weight: 8,
49
+ properties: {}
50
+ },
51
+ {
52
+ event: "qux",
53
+ weight: 7,
54
+ properties: {}
55
+ },
56
+ {
57
+ event: "garply",
58
+ weight: 6,
59
+ properties: {}
60
+ },
61
+ {
62
+ event: "durtle",
63
+ weight: 5,
64
+ properties: {}
65
+ },
66
+ {
67
+ event: "linny",
68
+ weight: 4,
69
+ properties: {}
70
+ },
71
+ {
72
+ event: "fonk",
73
+ weight: 3,
74
+ properties: {}
75
+ },
76
+ {
77
+ event: "crumn",
78
+ weight: 2,
79
+ properties: {}
80
+ },
81
+ {
82
+ event: "yak",
83
+ weight: 1,
84
+ properties: {}
85
+ }
86
+ ],
87
+ superProps: {
88
+ color: ["red", "orange", "yellow", "green", "blue", "indigo", "violet"],
89
+ number: integer,
90
+
91
+ },
92
+ userProps: {
93
+ title: chance.profession.bind(chance),
94
+ luckyNumber: weightedRange(42, 420),
95
+ spiritAnimal: ["duck", "dog", "otter", "penguin", "cat", "elephant", "lion", "cheetah", "giraffe", "zebra", "rhino", "hippo", "whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "starfish", "seahorse", "crab", "lobster", "shrimp", "clam", "snail", "slug", "butterfly", "moth", "bee", "wasp", "ant", "beetle", "ladybug", "caterpillar", "centipede", "millipede", "scorpion", "spider", "tarantula", "tick", "mite", "mosquito", "fly", "dragonfly", "damselfly", "grasshopper", "cricket", "locust", "mantis", "cockroach", "termite", "praying mantis", "walking stick", "stick bug", "leaf insect", "lacewing", "aphid", "cicada", "thrips", "psyllid", "scale insect", "whitefly", "mealybug", "planthopper", "leafhopper", "treehopper", "flea", "louse", "bedbug", "flea beetle", "weevil", "longhorn beetle", "leaf beetle", "tiger beetle", "ground beetle", "lady beetle", "firefly", "click beetle", "rove beetle", "scarab beetle", "dung beetle", "stag beetle", "rhinoceros beetle", "hercules beetle", "goliath beetle", "jewel beetle", "tortoise beetle"]
96
+ },
97
+
98
+ scdProps: {},
99
+ mirrorProps: {},
100
+ groupKeys: [],
101
+ groupProps: {},
102
+ lookupTables: [],
103
+ hook: function (record, type, meta) {
104
+ return record;
105
+ }
106
+ };
107
+
108
+
109
+
110
+ module.exports = config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-mp-data",
3
- "version": "1.3.03",
3
+ "version": "1.3.04",
4
4
  "description": "builds all mixpanel primitives for a given project",
5
5
  "main": "index.js",
6
6
  "types": "types.d.ts",
@@ -46,5 +46,8 @@
46
46
  "mixpanel-import": "^2.5.51",
47
47
  "papaparse": "^5.4.1",
48
48
  "yargs": "^17.7.2"
49
+ },
50
+ "devDependencies": {
51
+ "@types/jest": "^29.5.12"
49
52
  }
50
53
  }
@@ -25,7 +25,7 @@ describe('timeSoup', () => {
25
25
 
26
26
 
27
27
 
28
- const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer, makeHashTags, makeProducts, mapToRange, person, pick, range, weighList, weightedRange } = require('../utils');
28
+ const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer, mapToRange, person, pick, range, weighList, weightedRange } = require('../utils');
29
29
 
30
30
  describe('utils', () => {
31
31
 
@@ -124,8 +124,8 @@ describe('utils', () => {
124
124
  const sd = 5;
125
125
  const mappedValue = mapToRange(value, mean, sd);
126
126
  expect(mappedValue).toBe(10);
127
- });
128
-
127
+ });
128
+
129
129
 
130
130
 
131
131
  test('exhaust: elements', () => {
@@ -184,15 +184,24 @@ describe('utils', () => {
184
184
  test('emoji: works', () => {
185
185
  const emojis = generateEmoji(5)();
186
186
  expect(typeof emojis).toBe('string');
187
- expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
187
+ if (!Array.isArray(emojis)) {
188
+ expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
189
+ }
190
+ if (Array.isArray(emojis)) {
191
+ expect(emojis.length).toBeLessThanOrEqual(5);
192
+ }
188
193
  });
189
194
 
190
195
  test('emoji: length', () => {
191
196
  const result = generateEmoji();
192
197
  const emojis = result();
193
198
  expect(typeof emojis).toBe('string');
194
- const emojiArray = emojis.split(', ');
195
- expect(emojiArray.length).toBeLessThanOrEqual(10); // Assuming max default is 10
199
+ if (!Array.isArray(emojis)) {
200
+ expect(emojis.split(', ').length).toBeLessThanOrEqual(10);
201
+ }
202
+ if (Array.isArray(emojis)) {
203
+ expect(emojis.length).toBeLessThanOrEqual(10);
204
+ }
196
205
 
197
206
  });
198
207
 
package/timesoup.js CHANGED
@@ -22,10 +22,10 @@ const PEAK_DAYS = [
22
22
 
23
23
  /**
24
24
  * essentially, a timestamp generator with a twist
25
- * @param {number} earliestTime - The earliest timestamp in Unix format.
26
- * @param {number} latestTime - The latest timestamp in Unix format.
27
- * @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
28
- * @returns {number} - The generated event timestamp in Unix format.
25
+ * @param {number} [earliestTime] - The earliest timestamp in Unix format.
26
+ * @param {number} [latestTime] - The latest timestamp in Unix format.
27
+ * @param {Array} [peakDays] - Array of Unix timestamps representing the start of peak days.
28
+ * @returns {string} - The generated event timestamp in Unix format.
29
29
  */
30
30
  function AKsTimeSoup(earliestTime, latestTime = NOW, peakDays = PEAK_DAYS) {
31
31
  let chosenTime;
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "noEmit": true,
8
+ "strict": false,
9
+ "esModuleInterop": true,
10
+ "resolveJsonModule": true
11
+ },
12
+ "include": [
13
+ "**/*.js"
14
+ ],
15
+ "exclude": [
16
+ "node_modules"
17
+ ]
18
+ }
package/types.d.ts CHANGED
@@ -1,114 +1,128 @@
1
1
  declare namespace main {
2
- type Primitives = string | number | boolean | Date | Record<string, any>;
3
-
4
- // Recursive type to handle functions returning functions that eventually return Primitives or arrays of Primitives
5
- type ValueValid =
6
- | Primitives
7
- | ValueValid[]
8
- | (() => ValueValid);
9
-
10
- // MAIN CONFIGURATION OBJECT
11
- export interface Config {
12
- token?: string;
13
- seed?: string;
14
- numDays?: number;
15
- numEvents?: number;
16
- numUsers?: number;
17
- format?: "csv" | "json";
18
- region?: string;
19
- events?: EventConfig[];
20
- superProps?: Record<string, ValueValid>;
21
- userProps?: Record<string, ValueValid>;
22
- scdProps?: Record<string, ValueValid>;
23
- mirrorProps?: Record<string, MirrorProps>;
24
- groupKeys?: [string, number][];
25
- groupProps?: Record<string, Record<string, ValueValid>>;
26
- lookupTables?: LookupTable[];
27
- writeToDisk?: boolean;
28
- simulationName?: string;
29
- verbose?: boolean;
30
- anonIds?: boolean;
31
- sessionIds?: boolean;
32
- hook?: Hook;
33
- }
34
-
35
- export type Hook = (record: any, type: string, meta: any) => any;
36
-
37
- export interface EventConfig {
38
- event?: string;
39
- weight?: number;
40
- properties?: Record<string, ValueValid>;
41
- isFirstEvent?: boolean;
42
- }
43
-
44
- export interface MirrorProps {
45
- events: string[] | "*";
46
- values: ValueValid[];
47
- }
48
-
49
- export interface LookupTable {
50
- key: string;
51
- entries: number;
52
- attributes: Record<string, ValueValid>;
53
- }
54
-
55
- export interface SCDTable {
56
- distinct_id: string;
57
- insertTime: string;
58
- startTime: string;
59
- [key: string]: ValueValid;
60
- }
61
-
62
- export type Result = {
63
- eventData: EventData[];
64
- userProfilesData: any[];
65
- scdTableData: any[];
66
- groupProfilesData: GroupProfilesData[];
67
- lookupTableData: LookupTableData[];
68
- import?: ImportResults;
69
- files?: string[];
70
- };
71
-
72
- export interface EventData {
73
- event: string;
74
- $source: string;
75
- time: string;
76
- $device_id?: string;
77
- $session_id?: string;
78
- $user_id?: string;
79
- [key: string]: any;
80
- }
81
-
82
- export interface GroupProfilesData {
83
- key: string;
84
- data: any[];
85
- }
86
-
87
- export interface LookupTableData {
88
- key: string;
89
- data: any[];
90
- }
91
-
92
- export interface ImportResults {
93
- events: ImportResult;
94
- users: ImportResult;
95
- groups: ImportResult[];
96
- }
97
-
98
- export interface ImportResult {
99
- success: number;
100
- bytes: number;
101
- }
2
+ type Primitives = string | number | boolean | Date | Record<string, any>;
3
+
4
+ // Recursive type to handle functions returning functions that eventually return Primitives or arrays of Primitives
5
+ export type ValueValid = Primitives | ValueValid[] | (() => ValueValid);
6
+
7
+ // MAIN CONFIGURATION OBJECT
8
+ export interface Config {
9
+ token?: string;
10
+ seed?: string;
11
+ numDays?: number;
12
+ numEvents?: number;
13
+ numUsers?: number;
14
+ format?: "csv" | "json";
15
+ region?: "US" | "EU";
16
+ events?: EventConfig[];
17
+ superProps?: Record<string, ValueValid>;
18
+ userProps?: Record<string, ValueValid>;
19
+ scdProps?: Record<string, ValueValid>;
20
+ mirrorProps?: Record<string, MirrorProps>;
21
+ groupKeys?: [string, number][] | [string, number, string[]][];
22
+ groupProps?: Record<string, Record<string, ValueValid>>;
23
+ lookupTables?: LookupTable[];
24
+ writeToDisk?: boolean;
25
+ simulationName?: string;
26
+ verbose?: boolean;
27
+ anonIds?: boolean;
28
+ sessionIds?: boolean;
29
+ hook?: Hook<any>;
102
30
  }
103
-
104
- /**
105
- * Mixpanel Data Generator
106
- * model events, users, groups, and lookup tables (and SCD props!)
107
- * @example
108
- * const gen = require('make-mp-data')
109
- * const dta = gen({writeToDisk: false})
110
- */
111
- declare function main(config: main.Config): Promise<main.Result>;
112
-
113
- export = main;
114
-
31
+
32
+ export type Hook<T> = (record: any, type: string, meta: any) => T;
33
+
34
+ export interface EnrichArrayOptions<T> {
35
+ hook?: Hook<T>;
36
+ type?: string;
37
+ [key: string]: any;
38
+ }
39
+
40
+ export interface EnrichedArray<T> extends Array<T> {
41
+ hPush: (item: T) => number;
42
+ }
43
+
44
+ export interface EventConfig {
45
+ event?: string;
46
+ weight?: number;
47
+ properties?: Record<string, ValueValid>;
48
+ isFirstEvent?: boolean;
49
+ }
50
+
51
+ export interface MirrorProps {
52
+ events: string[] | "*";
53
+ values: ValueValid[];
54
+ }
55
+
56
+ export interface LookupTable {
57
+ key: string;
58
+ entries: number;
59
+ attributes: Record<string, ValueValid>;
60
+ }
61
+
62
+ export interface SCDTable {
63
+ distinct_id: string;
64
+ insertTime: string;
65
+ startTime: string;
66
+ [key: string]: ValueValid;
67
+ }
68
+
69
+ export type Result = {
70
+ eventData: EventData[];
71
+ userProfilesData: any[];
72
+ scdTableData: any[];
73
+ groupProfilesData: GroupProfilesData[];
74
+ lookupTableData: LookupTableData[];
75
+ import?: ImportResults;
76
+ files?: string[];
77
+ };
78
+
79
+ export interface EventData {
80
+ event: string;
81
+ $source: string;
82
+ time: string;
83
+ $device_id?: string;
84
+ $session_id?: string;
85
+ $user_id?: string;
86
+ [key: string]: any;
87
+ }
88
+
89
+ export interface GroupProfilesData {
90
+ key: string;
91
+ data: any[];
92
+ }
93
+
94
+ export interface LookupTableData {
95
+ key: string;
96
+ data: any[];
97
+ }
98
+
99
+ export interface ImportResults {
100
+ events: ImportResult;
101
+ users: ImportResult;
102
+ groups: ImportResult[];
103
+ }
104
+
105
+ export interface ImportResult {
106
+ success: number;
107
+ bytes: number;
108
+ }
109
+ export interface Person {
110
+ $name: string;
111
+ $email: string;
112
+ $avatar: string;
113
+ $created: string | undefined;
114
+ anonymousIds: string[];
115
+ sessionIds: string[];
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Mixpanel Data Generator
121
+ * model events, users, groups, and lookup tables (and SCD props!)
122
+ * @example
123
+ * const gen = require('make-mp-data')
124
+ * const dta = gen({writeToDisk: false})
125
+ */
126
+ declare function main(config: main.Config): Promise<main.Result>;
127
+
128
+ export = main;
package/utils.js CHANGED
@@ -1,3 +1,5 @@
1
+ const fs = require('fs');
2
+ const Papa = require('papaparse');
1
3
  const Chance = require('chance');
2
4
  const chance = new Chance();
3
5
  const readline = require('readline');
@@ -5,8 +7,10 @@ const { comma, uid } = require('ak-tools');
5
7
  const { spawn } = require('child_process');
6
8
  const dayjs = require('dayjs');
7
9
  const utc = require('dayjs/plugin/utc');
10
+
8
11
  dayjs.extend(utc);
9
12
 
13
+
10
14
  function pick(items) {
11
15
  if (!Array.isArray(items)) {
12
16
  if (typeof items === 'function') {
@@ -57,10 +61,9 @@ function date(inTheLast = 30, isPast = true, format = 'YYYY-MM-DD') {
57
61
  function dates(inTheLast = 30, numPairs = 5, format = 'YYYY-MM-DD') {
58
62
  const pairs = [];
59
63
  for (let i = 0; i < numPairs; i++) {
60
- pairs.push([date(inTheLast, format), date(inTheLast, format)]);
64
+ pairs.push([date(inTheLast, true, format), date(inTheLast, true, format)]);
61
65
  }
62
66
  return pairs;
63
-
64
67
  };
65
68
 
66
69
  function day(start, end) {
@@ -96,11 +99,15 @@ function choose(value) {
96
99
  return value;
97
100
  }
98
101
 
102
+ if (typeof value === 'number') {
103
+ return value;
104
+ }
105
+
99
106
  // If it's not a function or array, return it as is
100
107
  return value;
101
108
  }
102
109
  catch (e) {
103
- console.error(`\n\nerror on value: ${value};\n\n`,e, '\n\n');
110
+ console.error(`\n\nerror on value: ${value};\n\n`, e, '\n\n');
104
111
  return '';
105
112
  }
106
113
  }
@@ -111,7 +118,7 @@ function exhaust(arr) {
111
118
  };
112
119
 
113
120
 
114
- function integer(min, max) {
121
+ function integer(min = 1, max = 100) {
115
122
  if (min === max) {
116
123
  return min;
117
124
  }
@@ -178,22 +185,23 @@ function unOptimizedWeightedRange(min, max, size = 100, skew = 1) {
178
185
 
179
186
  // optimized weighted range
180
187
  function weightedRange(min, max, size = 100, skew = 1) {
181
- const mean = (max + min) / 2;
182
- const sd = (max - min) / 4;
183
- const array = [];
184
- while (array.length < size) {
185
- const normalValue = boxMullerRandom();
186
- const skewedValue = applySkew(normalValue, skew);
187
- const mappedValue = mapToRange(skewedValue, mean, sd);
188
- if (mappedValue >= min && mappedValue <= max) {
189
- array.push(mappedValue);
190
- }
191
- }
192
- return array;
188
+ const mean = (max + min) / 2;
189
+ const sd = (max - min) / 4;
190
+ const array = [];
191
+ while (array.length < size) {
192
+ const normalValue = boxMullerRandom();
193
+ const skewedValue = applySkew(normalValue, skew);
194
+ const mappedValue = mapToRange(skewedValue, mean, sd);
195
+ if (mappedValue >= min && mappedValue <= max) {
196
+ array.push(mappedValue);
197
+ }
198
+ }
199
+ return array;
193
200
  }
194
201
 
195
202
 
196
203
  function progress(thing, p) {
204
+ // @ts-ignore
197
205
  readline.cursorTo(process.stdout, 0);
198
206
  process.stdout.write(`${thing} processed ... ${comma(p)}`);
199
207
  };
@@ -226,7 +234,12 @@ function getUniqueKeys(data) {
226
234
  return Array.from(keysSet);
227
235
  };
228
236
 
229
- //makes a random-sized array of emojis
237
+ //
238
+ /**
239
+ * makes a random-sized array of emojis
240
+ * @param {number} max=10
241
+ * @param {boolean} array=false
242
+ */
230
243
  function generateEmoji(max = 10, array = false) {
231
244
  return function () {
232
245
  const emojis = ['๐Ÿ˜€', '๐Ÿ˜‚', '๐Ÿ˜', '๐Ÿ˜Ž', '๐Ÿ˜œ', '๐Ÿ˜‡', '๐Ÿ˜ก', '๐Ÿ˜ฑ', '๐Ÿ˜ญ', '๐Ÿ˜ด', '๐Ÿคข', '๐Ÿค ', '๐Ÿคก', '๐Ÿ‘ฝ', '๐Ÿ‘ป', '๐Ÿ’ฉ', '๐Ÿ‘บ', '๐Ÿ‘น', '๐Ÿ‘พ', '๐Ÿค–', '๐Ÿค‘', '๐Ÿค—', '๐Ÿค“', '๐Ÿค”', '๐Ÿค', '๐Ÿ˜€', '๐Ÿ˜‚', '๐Ÿ˜', '๐Ÿ˜Ž', '๐Ÿ˜œ', '๐Ÿ˜‡', '๐Ÿ˜ก', '๐Ÿ˜ฑ', '๐Ÿ˜ญ', '๐Ÿ˜ด', '๐Ÿคข', '๐Ÿค ', '๐Ÿคก', '๐Ÿ‘ฝ', '๐Ÿ‘ป', '๐Ÿ’ฉ', '๐Ÿ‘บ', '๐Ÿ‘น', '๐Ÿ‘พ', '๐Ÿค–', '๐Ÿค‘', '๐Ÿค—', '๐Ÿค“', '๐Ÿค”', '๐Ÿค', '๐Ÿ˜ˆ', '๐Ÿ‘ฟ', '๐Ÿ‘ฆ', '๐Ÿ‘ง', '๐Ÿ‘จ', '๐Ÿ‘ฉ', '๐Ÿ‘ด', '๐Ÿ‘ต', '๐Ÿ‘ถ', '๐Ÿง’', '๐Ÿ‘ฎ', '๐Ÿ‘ท', '๐Ÿ’‚', '๐Ÿ•ต', '๐Ÿ‘ฉโ€โš•๏ธ', '๐Ÿ‘จโ€โš•๏ธ', '๐Ÿ‘ฉโ€๐ŸŒพ', '๐Ÿ‘จโ€๐ŸŒพ', '๐Ÿ‘ฉโ€๐Ÿณ', '๐Ÿ‘จโ€๐Ÿณ', '๐Ÿ‘ฉโ€๐ŸŽ“', '๐Ÿ‘จโ€๐ŸŽ“', '๐Ÿ‘ฉโ€๐ŸŽค', '๐Ÿ‘จโ€๐ŸŽค', '๐Ÿ‘ฉโ€๐Ÿซ', '๐Ÿ‘จโ€๐Ÿซ', '๐Ÿ‘ฉโ€๐Ÿญ', '๐Ÿ‘จโ€๐Ÿญ', '๐Ÿ‘ฉโ€๐Ÿ’ป', '๐Ÿ‘จโ€๐Ÿ’ป', '๐Ÿ‘ฉโ€๐Ÿ’ผ', '๐Ÿ‘จโ€๐Ÿ’ผ', '๐Ÿ‘ฉโ€๐Ÿ”ง', '๐Ÿ‘จโ€๐Ÿ”ง', '๐Ÿ‘ฉโ€๐Ÿ”ฌ', '๐Ÿ‘จโ€๐Ÿ”ฌ', '๐Ÿ‘ฉโ€๐ŸŽจ', '๐Ÿ‘จโ€๐ŸŽจ', '๐Ÿ‘ฉโ€๐Ÿš’', '๐Ÿ‘จโ€๐Ÿš’', '๐Ÿ‘ฉโ€โœˆ๏ธ', '๐Ÿ‘จโ€โœˆ๏ธ', '๐Ÿ‘ฉโ€๐Ÿš€', '๐Ÿ‘จโ€๐Ÿš€', '๐Ÿ‘ฉโ€โš–๏ธ', '๐Ÿ‘จโ€โš–๏ธ', '๐Ÿคถ', '๐ŸŽ…', '๐Ÿ‘ธ', '๐Ÿคด', '๐Ÿ‘ฐ', '๐Ÿคต', '๐Ÿ‘ผ', '๐Ÿคฐ', '๐Ÿ™‡', '๐Ÿ’', '๐Ÿ™…', '๐Ÿ™†', '๐Ÿ™‹', '๐Ÿคฆ', '๐Ÿคท', '๐Ÿ™Ž', '๐Ÿ™', '๐Ÿ’‡', '๐Ÿ’†', '๐Ÿ•ด', '๐Ÿ’ƒ', '๐Ÿ•บ', '๐Ÿšถ', '๐Ÿƒ', '๐Ÿคฒ', '๐Ÿ‘', '๐Ÿ™Œ', '๐Ÿ‘', '๐Ÿค', '๐Ÿ‘', '๐Ÿ‘Ž', '๐Ÿ‘Š', 'โœŠ', '๐Ÿค›', '๐Ÿคœ', '๐Ÿคž', 'โœŒ๏ธ', '๐ŸคŸ', '๐Ÿค˜', '๐Ÿ‘Œ', '๐Ÿ‘ˆ', '๐Ÿ‘‰', '๐Ÿ‘†', '๐Ÿ‘‡', 'โ˜๏ธ', 'โœ‹', '๐Ÿคš', '๐Ÿ–', '๐Ÿ––', '๐Ÿ‘‹', '๐Ÿค™', '๐Ÿ’ช', '๐Ÿ–•', 'โœ๏ธ', '๐Ÿคณ', '๐Ÿ’…', '๐Ÿ‘‚', '๐Ÿ‘ƒ', '๐Ÿ‘ฃ', '๐Ÿ‘€', '๐Ÿ‘', '๐Ÿง ', '๐Ÿ‘…', '๐Ÿ‘„', '๐Ÿ’‹', '๐Ÿ‘“', '๐Ÿ•ถ', '๐Ÿ‘”', '๐Ÿ‘•', '๐Ÿ‘–', '๐Ÿงฃ', '๐Ÿงค', '๐Ÿงฅ', '๐Ÿงฆ', '๐Ÿ‘—', '๐Ÿ‘˜', '๐Ÿ‘™', '๐Ÿ‘š', '๐Ÿ‘›', '๐Ÿ‘œ', '๐Ÿ‘', '๐Ÿ›', '๐ŸŽ’', '๐Ÿ‘ž', '๐Ÿ‘Ÿ', '๐Ÿ‘ ', '๐Ÿ‘ก', '๐Ÿ‘ข', '๐Ÿ‘‘', '๐Ÿ‘’', '๐ŸŽฉ', '๐ŸŽ“', '๐Ÿงข', 'โ›‘', '๐Ÿ“ฟ', '๐Ÿ’„', '๐Ÿ’', '๐Ÿ’Ž', '๐Ÿ”‡', '๐Ÿ”ˆ', '๐Ÿ”‰', '๐Ÿ”Š', '๐Ÿ“ข', '๐Ÿ“ฃ', '๐Ÿ“ฏ', '๐Ÿ””', '๐Ÿ”•', '๐ŸŽผ', '๐ŸŽต', '๐ŸŽถ', '๐ŸŽ™', '๐ŸŽš', '๐ŸŽ›', '๐ŸŽค', '๐ŸŽง', '๐Ÿ“ป', '๐ŸŽท', '๐ŸŽธ', '๐ŸŽน', '๐ŸŽบ', '๐ŸŽป', '๐Ÿฅ', '๐Ÿ“ฑ', '๐Ÿ“ฒ', '๐Ÿ’ป', '๐Ÿ–ฅ', '๐Ÿ–จ', '๐Ÿ–ฑ', '๐Ÿ–ฒ', '๐Ÿ•น', '๐Ÿ—œ', '๐Ÿ’ฝ', '๐Ÿ’พ', '๐Ÿ’ฟ', '๐Ÿ“€', '๐Ÿ“ผ', '๐Ÿ“ท', '๐Ÿ“ธ', '๐Ÿ“น', '๐ŸŽฅ', '๐Ÿ“ฝ', '๐ŸŽž', '๐Ÿ“ž', 'โ˜Ž๏ธ', '๐Ÿ“Ÿ', '๐Ÿ“ ', '๐Ÿ“บ', '๐Ÿ“ป', '๐ŸŽ™', '๐Ÿ“ก', '๐Ÿ”', '๐Ÿ”Ž', '๐Ÿ”ฌ', '๐Ÿ”ญ', '๐Ÿ“ก', '๐Ÿ’ก', '๐Ÿ”ฆ', '๐Ÿฎ', '๐Ÿ“”', '๐Ÿ“•', '๐Ÿ“–', '๐Ÿ“—', '๐Ÿ“˜', '๐Ÿ“™', '๐Ÿ“š', '๐Ÿ““', '๐Ÿ“’', '๐Ÿ“ƒ', '๐Ÿ“œ', '๐Ÿ“„', '๐Ÿ“ฐ', '๐Ÿ—ž', '๐Ÿ“‘', '๐Ÿ”–', '๐Ÿท', '๐Ÿ’ฐ', '๐Ÿ’ด', '๐Ÿ’ต', '๐Ÿ’ถ', '๐Ÿ’ท', '๐Ÿ’ธ', '๐Ÿ’ณ', '๐Ÿงพ', '๐Ÿ’น', '๐Ÿ’ฑ', '๐Ÿ’ฒ', 'โœ‰๏ธ', '๐Ÿ“ง', '๐Ÿ“จ', '๐Ÿ“ฉ', '๐Ÿ“ค', '๐Ÿ“ฅ', '๐Ÿ“ฆ', '๐Ÿ“ซ', '๐Ÿ“ช', '๐Ÿ“ฌ', '๐Ÿ“ญ', '๐Ÿ“ฎ', '๐Ÿ—ณ', 'โœ๏ธ', 'โœ’๏ธ', '๐Ÿ–‹', '๐Ÿ–Š', '๐Ÿ–Œ', '๐Ÿ–', '๐Ÿ“', '๐Ÿ’ผ', '๐Ÿ“', '๐Ÿ“‚', '๐Ÿ—‚', '๐Ÿ“…', '๐Ÿ“†', '๐Ÿ—’', '๐Ÿ—“', '๐Ÿ“‡', '๐Ÿ“ˆ', '๐Ÿ“‰', '๐Ÿ“Š', '๐Ÿ“‹', '๐Ÿ“Œ', '๐Ÿ“', '๐Ÿ“Ž', '๐Ÿ–‡', '๐Ÿ“', '๐Ÿ“', 'โœ‚๏ธ', '๐Ÿ—ƒ', '๐Ÿ—„', '๐Ÿ—‘', '๐Ÿ”’', '๐Ÿ”“', '๐Ÿ”', '๐Ÿ”', '๐Ÿ”‘', '๐Ÿ—', '๐Ÿ”จ', 'โ›', 'โš’', '๐Ÿ› ', '๐Ÿ—ก', 'โš”๏ธ', '๐Ÿ”ซ', '๐Ÿน', '๐Ÿ›ก', '๐Ÿ”ง', '๐Ÿ”ฉ', 'โš™๏ธ', '๐Ÿ—œ', 'โš–๏ธ', '๐Ÿ”—', 'โ›“', '๐Ÿงฐ', '๐Ÿงฒ', 'โš—๏ธ', '๐Ÿงช', '๐Ÿงซ', '๐Ÿงฌ', '๐Ÿ”ฌ', '๐Ÿ”ญ', '๐Ÿ“ก', '๐Ÿ’‰', '๐Ÿ’Š', '๐Ÿ›', '๐Ÿ›‹', '๐Ÿšช', '๐Ÿšฝ', '๐Ÿšฟ', '๐Ÿ›', '๐Ÿงด', '๐Ÿงท', '๐Ÿงน', '๐Ÿงบ', '๐Ÿงป', '๐Ÿงผ', '๐Ÿงฝ', '๐Ÿงฏ', '๐Ÿšฌ', 'โšฐ๏ธ', 'โšฑ๏ธ', '๐Ÿ—ฟ', '๐Ÿบ', '๐Ÿงฑ', '๐ŸŽˆ', '๐ŸŽ', '๐ŸŽ€', '๐ŸŽ', '๐ŸŽŠ', '๐ŸŽ‰', '๐ŸŽŽ', '๐Ÿฎ', '๐ŸŽ', '๐Ÿงง', 'โœ‰๏ธ', '๐Ÿ“ฉ', '๐Ÿ“จ', '๐Ÿ“ง'];
@@ -241,10 +254,17 @@ function generateEmoji(max = 10, array = false) {
241
254
  };
242
255
  };
243
256
 
257
+ /** @typedef {import('./types').Person} Person */
244
258
 
259
+ /**
260
+ * @param {number} bornDaysAgo=30
261
+ * @return {Person}
262
+ */
245
263
  function person(bornDaysAgo = 30) {
246
264
  //names and photos
247
- const gender = chance.pickone(['male', 'female']);
265
+ let gender = chance.pickone(['male', 'female']);
266
+ if (!gender) gender = "female";
267
+ // @ts-ignore
248
268
  const first = chance.first({ gender });
249
269
  const last = chance.last();
250
270
  const $name = `${first} ${last}`;
@@ -256,33 +276,35 @@ function person(bornDaysAgo = 30) {
256
276
  });
257
277
  const avPath = gender === 'male' ? `/men/${randomAvatarNumber}.jpg` : `/women/${randomAvatarNumber}.jpg`;
258
278
  const $avatar = avatarPrefix + avPath;
259
- const $created = date(bornDaysAgo, true, null)();
279
+ const $created = date(bornDaysAgo, true)();
260
280
 
281
+ /** @type {Person} */
261
282
  const user = {
262
283
  $name,
263
284
  $email,
264
285
  $avatar,
265
286
  $created,
287
+ anonymousIds: [],
288
+ sessionIds: []
266
289
  };
267
290
 
268
291
  //anon Ids
269
292
  if (global.MP_SIMULATION_CONFIG?.anonIds) {
270
- const anonymousIds = [];
271
293
  const clusterSize = integer(2, 10);
272
294
  for (let i = 0; i < clusterSize; i++) {
273
- anonymousIds.push(uid(42));
295
+ const anonId = uid(42);
296
+ user.anonymousIds.push(anonId);
274
297
  }
275
- user.anonymousIds = anonymousIds;
298
+
276
299
  }
277
300
 
278
301
  //session Ids
279
302
  if (global.MP_SIMULATION_CONFIG?.sessionIds) {
280
- const sessionIds = [];
281
303
  const sessionSize = integer(5, 30);
282
304
  for (let i = 0; i < sessionSize; i++) {
283
- sessionIds.push([uid(5), uid(5), uid(5), uid(5)].join("-"));
305
+ const sessionId = [uid(5), uid(5), uid(5), uid(5)].join("-");
306
+ user.sessionIds.push(sessionId);
284
307
  }
285
- user.sessionIds = sessionIds;
286
308
  }
287
309
 
288
310
  return user;
@@ -316,6 +338,51 @@ function weighList(items, mostChosenIndex) {
316
338
  };
317
339
  }
318
340
 
341
+
342
+
343
+
344
+ function streamJSON(path, data) {
345
+ return new Promise((resolve, reject) => {
346
+ const writeStream = fs.createWriteStream(path, { encoding: 'utf8' });
347
+ data.forEach(item => {
348
+ writeStream.write(JSON.stringify(item) + '\n');
349
+ });
350
+ writeStream.end();
351
+ writeStream.on('finish', () => {
352
+ resolve(path);
353
+ });
354
+ writeStream.on('error', reject);
355
+ });
356
+ }
357
+
358
+ function streamCSV(path, data) {
359
+ return new Promise((resolve, reject) => {
360
+ const writeStream = fs.createWriteStream(path, { encoding: 'utf8' });
361
+ // Extract all unique keys from the data array
362
+ const columns = getUniqueKeys(data); // Assuming getUniqueKeys properly retrieves all keys
363
+
364
+ // Stream the header
365
+ writeStream.write(columns.join(',') + '\n');
366
+
367
+ // Stream each data row
368
+ data.forEach(item => {
369
+ for (const key in item) {
370
+ // Ensure all nested objects are properly stringified
371
+ if (typeof item[key] === "object") item[key] = JSON.stringify(item[key]);
372
+ }
373
+ const row = columns.map(col => item[col] ? `"${item[col].toString().replace(/"/g, '""')}"` : "").join(',');
374
+ writeStream.write(row + '\n');
375
+ });
376
+
377
+ writeStream.end();
378
+ writeStream.on('finish', () => {
379
+ resolve(path);
380
+ });
381
+ writeStream.on('error', reject);
382
+ });
383
+ }
384
+
385
+
319
386
  module.exports = {
320
387
  pick,
321
388
  date,
@@ -335,5 +402,9 @@ module.exports = {
335
402
  getUniqueKeys,
336
403
  generateEmoji,
337
404
  person,
338
- weighList
405
+ weighList,
406
+
407
+
408
+ streamJSON,
409
+ streamCSV
339
410
  };