make-mp-data 1.3.2 โ†’ 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;
@@ -77,7 +75,6 @@ async function main(config) {
77
75
  numEvents,
78
76
  numUsers,
79
77
  numDays,
80
- events,
81
78
  anonIds,
82
79
  sessionIds,
83
80
  format,
@@ -89,7 +86,7 @@ async function main(config) {
89
86
  });
90
87
  log(`------------------SETUP------------------`);
91
88
  log(`\nyour data simulation will heretofore be known as: \n\n\t${simulationName.toUpperCase()}...\n`);
92
- 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));
93
90
  log(`------------------SETUP------------------`, "\n");
94
91
 
95
92
 
@@ -117,11 +114,13 @@ async function main(config) {
117
114
  const weight = event.weight || 1;
118
115
  for (let i = 0; i < weight; i++) {
119
116
 
117
+ // @ts-ignore
120
118
  acc.push(event);
121
119
  }
122
120
  return acc;
123
121
  }, [])
124
122
 
123
+ // @ts-ignore
125
124
  .filter((e) => !e.isFirstEvent);
126
125
 
127
126
  const firstEvents = events.filter((e) => e.isFirstEvent);
@@ -257,7 +256,6 @@ async function main(config) {
257
256
  numEvents,
258
257
  numUsers,
259
258
  numDays,
260
- events,
261
259
  anonIds,
262
260
  sessionIds,
263
261
  format,
@@ -278,7 +276,8 @@ async function main(config) {
278
276
  };
279
277
  }
280
278
  log(`-----------------WRITES------------------`, `\n\n`);
281
- //write the files
279
+
280
+ let writeFilePromises = [];
282
281
  if (writeToDisk) {
283
282
  if (verbose) log(`writing files... for ${simulationName}`);
284
283
  loopFiles: for (const ENTITY of pairs) {
@@ -297,25 +296,16 @@ async function main(config) {
297
296
  log(`\twriting ${path}`);
298
297
  //if it's a lookup table, it's always a CSV
299
298
  if (format === "csv" || path.includes("-LOOKUP.csv")) {
300
- const columns = u.getUniqueKeys(TABLE);
301
- //papa parse needs eac nested field JSON stringified
302
- TABLE.forEach((e) => {
303
- for (const key in e) {
304
- if (typeof e[key] === "object") e[key] = JSON.stringify(e[key]);
305
- }
306
- });
307
-
308
- const csv = Papa.unparse(TABLE, { columns });
309
- await touch(path, csv);
299
+ writeFilePromises.push(u.streamCSV(path, TABLE));
310
300
  }
311
301
  else {
312
- const ndjson = TABLE.map((d) => JSON.stringify(d)).join("\n");
313
- await touch(path, ndjson, false);
302
+ writeFilePromises.push(u.streamJSON(path, TABLE));
314
303
  }
315
304
 
316
305
  }
317
306
  }
318
307
  }
308
+ const fileWriteResults = await Promise.all(writeFilePromises);
319
309
 
320
310
  const importResults = { events: {}, users: {}, groups: [] };
321
311
 
@@ -404,6 +394,10 @@ function makeProfile(props, defaults) {
404
394
  ...defaults,
405
395
  };
406
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
+
407
401
  for (const key in props) {
408
402
  try {
409
403
  profile[key] = u.choose(props[key]);
@@ -415,7 +409,7 @@ function makeProfile(props, defaults) {
415
409
  return profile;
416
410
  }
417
411
  /**
418
- * @param {import('./types.d.ts').valueValid} prop
412
+ * @param {import('./types.d.ts').ValueValid} prop
419
413
  * @param {string} scdKey
420
414
  * @param {string} distinct_id
421
415
  * @param {number} mutations
@@ -498,8 +492,11 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
498
492
  for (const groupPair of groupKeys) {
499
493
  const groupKey = groupPair[0];
500
494
  const groupCardinality = groupPair[1];
495
+ const groupEvents = groupPair[2] || [];
501
496
 
502
- 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));
503
500
  }
504
501
 
505
502
  //make $insert_id
@@ -509,18 +506,21 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
509
506
  }
510
507
 
511
508
  function buildFileNames(config) {
512
- const { format = "csv", groupKeys = [], lookupTables = [] } = config;
513
- const extension = format === "csv" ? "csv" : "json";
509
+ const { format = "csv", groupKeys = [], lookupTables = [], m } = config;
510
+ let extension = "";
511
+ extension = format === "csv" ? "csv" : "json";
514
512
  // const current = dayjs.utc().format("MM-DD-HH");
515
513
  const simName = config.simulationName;
516
514
  let writeDir = "./";
517
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");
518
518
 
519
519
  const writePaths = {
520
520
  eventFiles: [path.join(writeDir, `${simName}-EVENTS.${extension}`)],
521
521
  userFiles: [path.join(writeDir, `${simName}-USERS.${extension}`)],
522
522
  scdFiles: [],
523
- mirrorFiles: [path.join(writeDir, `${simName}-EVENTS-FUTURE-MIRROR.${extension}`)],
523
+ mirrorFiles: [],
524
524
  groupFiles: [],
525
525
  lookupFiles: [],
526
526
  folder: writeDir,
@@ -534,13 +534,16 @@ function buildFileNames(config) {
534
534
  );
535
535
  }
536
536
 
537
+ //add group files
537
538
  for (const groupPair of groupKeys) {
538
539
  const groupKey = groupPair[0];
540
+
539
541
  writePaths.groupFiles.push(
540
542
  path.join(writeDir, `${simName}-${groupKey}-GROUP.${extension}`)
541
543
  );
542
544
  }
543
545
 
546
+ //add lookup files
544
547
  for (const lookupTable of lookupTables) {
545
548
  const { key } = lookupTable;
546
549
  writePaths.lookupFiles.push(
@@ -549,10 +552,25 @@ function buildFileNames(config) {
549
552
  );
550
553
  }
551
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
+
552
563
  return writePaths;
553
564
  }
554
565
 
566
+ /** @typedef {import('./types').EnrichedArray} EnrichArray */
567
+ /** @typedef {import('./types').EnrichArrayOptions} EnrichArrayOptions */
555
568
 
569
+ /**
570
+ * @param {any[]} arr
571
+ * @param {EnrichArrayOptions} opts
572
+ * @returns {EnrichArray}}
573
+ */
556
574
  function enrichArray(arr = [], opts = {}) {
557
575
  const { hook = a => a, type = "", ...rest } = opts;
558
576
 
@@ -560,9 +578,15 @@ function enrichArray(arr = [], opts = {}) {
560
578
  return arr.push(hook(item, type, rest));
561
579
  }
562
580
 
563
- arr.hPush = transformThenPush;
581
+ /** @type {EnrichArray} */
582
+ // @ts-ignore
583
+ const enrichedArray = arr;
584
+
585
+
586
+ enrichedArray.hPush = transformThenPush;
587
+
564
588
 
565
- return arr;
589
+ return enrichedArray;
566
590
  };
567
591
 
568
592
 
@@ -571,7 +595,9 @@ function enrichArray(arr = [], opts = {}) {
571
595
  if (require.main === module) {
572
596
  isCLI = true;
573
597
  const args = cliParams();
574
- 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
575
601
  const suppliedConfig = args._[0];
576
602
 
577
603
  //if the user specifics an separate config file
@@ -605,6 +631,8 @@ if (require.main === module) {
605
631
  if (region) config.region = region;
606
632
  if (writeToDisk) config.writeToDisk = writeToDisk;
607
633
  if (writeToDisk === 'false') config.writeToDisk = false;
634
+ if (sessionIds) config.sessionIds = sessionIds;
635
+ if (anonIds) config.anonIds = anonIds;
608
636
  config.verbose = true;
609
637
 
610
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.02",
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
  };