make-mp-data 1.3.4 → 1.4.0

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.
@@ -1,19 +1,54 @@
1
1
  const generate = require('../index.js');
2
2
  const dayjs = require("dayjs");
3
3
  const utc = require("dayjs/plugin/utc");
4
+ const fs = require('fs');
4
5
  const u = require('ak-tools');
5
6
  dayjs.extend(utc);
6
- const { timeSoup } = generate;
7
7
  require('dotenv').config();
8
8
 
9
-
10
-
11
- describe('timeSoup', () => {
12
- test('always positive dates', () => {
9
+ const { applySkew,
10
+ boxMullerRandom,
11
+ choose,
12
+ date,
13
+ dates,
14
+ day,
15
+ exhaust,
16
+ generateEmoji,
17
+ getUniqueKeys,
18
+ integer,
19
+ mapToRange,
20
+ person,
21
+ pick,
22
+ range,
23
+ pickAWinner,
24
+ weightedRange,
25
+ enrichArray,
26
+ fixFirstAndLast,
27
+ generateUser,
28
+ openFinder,
29
+ progress,
30
+ shuffleArray,
31
+ shuffleExceptFirst,
32
+ shuffleExceptLast,
33
+ shuffleMiddle,
34
+ shuffleOutside,
35
+ streamCSV,
36
+ streamJSON,
37
+ weighArray,
38
+ weighFunnels,
39
+ buildFileNames,
40
+ TimeSoup,
41
+ getChance,
42
+ initChance
43
+ } = require('../utils');
44
+
45
+
46
+ describe('timesoup', () => {
47
+ test('always valid times', () => {
13
48
  const dates = [];
14
- for (let i = 0; i < 20000; i++) {
15
- const earliest = dayjs().subtract(u.rand(2, 360), 'D');
16
- dates.push(timeSoup());
49
+ for (let i = 0; i < 10000; i++) {
50
+ const earliest = dayjs().subtract(u.rand(5, 50), 'D');
51
+ dates.push(TimeSoup());
17
52
  }
18
53
  const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
19
54
  const badYear = dates.filter(d => !d.startsWith('202'));
@@ -24,8 +59,207 @@ describe('timeSoup', () => {
24
59
  });
25
60
 
26
61
 
62
+ describe('naming things', () => {
63
+
64
+ test('default config', () => {
65
+ const config = { simulationName: 'testSim' };
66
+ const result = buildFileNames(config);
67
+ expect(result.eventFiles).toEqual(['testSim-EVENTS.csv']);
68
+ expect(result.userFiles).toEqual(['testSim-USERS.csv']);
69
+ expect(result.scdFiles).toEqual([]);
70
+ expect(result.groupFiles).toEqual([]);
71
+ expect(result.lookupFiles).toEqual([]);
72
+ expect(result.mirrorFiles).toEqual([]);
73
+ expect(result.folder).toEqual('./');
74
+ });
75
+
76
+ test('json format', () => {
77
+ const config = { simulationName: 'testSim', format: 'json' };
78
+ const result = buildFileNames(config);
79
+ expect(result.eventFiles).toEqual(['testSim-EVENTS.json']);
80
+ expect(result.userFiles).toEqual(['testSim-USERS.json']);
81
+ });
82
+
83
+ test('with scdProps', () => {
84
+ const config = {
85
+ simulationName: 'testSim',
86
+ scdProps: { prop1: {}, prop2: {} }
87
+ };
88
+ const result = buildFileNames(config);
89
+ expect(result.scdFiles).toEqual([
90
+ 'testSim-prop1-SCD.csv',
91
+ 'testSim-prop2-SCD.csv'
92
+ ]);
93
+ });
94
+
95
+ test('with groupKeys', () => {
96
+ const config = {
97
+ simulationName: 'testSim',
98
+ groupKeys: [['group1'], ['group2']]
99
+ };
100
+ const result = buildFileNames(config);
101
+ expect(result.groupFiles).toEqual([
102
+ 'testSim-group1-GROUP.csv',
103
+ 'testSim-group2-GROUP.csv'
104
+ ]);
105
+ });
106
+
107
+ test('with lookupTables', () => {
108
+ const config = {
109
+ simulationName: 'testSim',
110
+ lookupTables: [{ key: 'lookup1' }, { key: 'lookup2' }]
111
+ };
112
+ const result = buildFileNames(config);
113
+ expect(result.lookupFiles).toEqual([
114
+ 'testSim-lookup1-LOOKUP.csv',
115
+ 'testSim-lookup2-LOOKUP.csv'
116
+ ]);
117
+ });
118
+
119
+ test('with mirrorProps', () => {
120
+ const config = {
121
+ simulationName: 'testSim',
122
+ mirrorProps: { prop1: {} }
123
+ };
124
+ const result = buildFileNames(config);
125
+ expect(result.mirrorFiles).toEqual(['testSim-MIRROR.csv']);
126
+ });
127
+
128
+ test('writeToDisk', async () => {
129
+ const config = { simulationName: 'testSim', writeToDisk: true };
130
+ const result = await buildFileNames(config);
131
+ expect(result.folder).toBeDefined();
132
+
133
+ });
134
+
135
+
136
+ test('invalid simName', () => {
137
+ const config = { simulationName: 123 };
138
+ expect(() => buildFileNames(config)).toThrow('simName must be a string');
139
+ });
140
+
141
+
142
+ test('streamJSON: writes to file', async () => {
143
+ const path = 'test.json';
144
+ const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
145
+ await streamJSON(path, data);
146
+ const content = fs.readFileSync(path, 'utf8');
147
+ const lines = content.trim().split('\n').map(line => JSON.parse(line));
148
+ expect(lines).toEqual(data);
149
+ fs.unlinkSync(path);
150
+ });
151
+
152
+ test('streamCSV: writes to file', async () => {
153
+ const path = 'test.csv';
154
+ const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
155
+ await streamCSV(path, data);
156
+ const content = fs.readFileSync(path, 'utf8');
157
+ const lines = content.trim().split('\n');
158
+ expect(lines.length).toBe(3); // Including header
159
+ fs.unlinkSync(path);
160
+ });
161
+
162
+
163
+ test('generateUser: works', () => {
164
+ const uuid = { guid: jest.fn().mockReturnValue('uuid-123') };
165
+ const numDays = 30;
166
+ const user = generateUser(numDays);
167
+ expect(user).toHaveProperty('distinct_id');
168
+ expect(user).toHaveProperty('name');
169
+ expect(user).toHaveProperty('email');
170
+ expect(user).toHaveProperty('avatar');
171
+ });
172
+
173
+ test('enrichArray: works', () => {
174
+ const arr = [];
175
+ const enrichedArray = enrichArray(arr);
176
+ enrichedArray.hookPush(1);
177
+ enrichedArray.hookPush(2);
178
+ const match = JSON.stringify(enrichedArray) === JSON.stringify([1, 2]);
179
+ expect(match).toEqual(true);
180
+ });
181
+
182
+ });
183
+
184
+
185
+ describe('determined random', () => {
186
+ test('initializes RNG with seed from environment variable', () => {
187
+ process.env.SEED = 'test-seed';
188
+ // @ts-ignore
189
+ initChance();
190
+ const chance = getChance();
191
+ expect(chance).toBeDefined();
192
+ expect(chance.random()).toBeGreaterThanOrEqual(0);
193
+ expect(chance.random()).toBeLessThanOrEqual(1);
194
+
195
+ });
196
+
197
+ test('initializes RNG only once', () => {
198
+ const seed = 'initial-seed';
199
+ initChance(seed);
200
+ const chance1 = getChance();
201
+ initChance('new-seed');
202
+ const chance2 = getChance();
203
+ expect(chance1).toBe(chance2);
204
+
205
+ });
206
+ });
207
+
208
+
209
+ describe('generateUser', () => {
210
+ test('creates a user with valid fields', () => {
211
+ const numDays = 30;
212
+ const user = generateUser('uuid-123', numDays);
213
+ expect(user).toHaveProperty('distinct_id');
214
+ expect(user).toHaveProperty('name');
215
+ expect(user).toHaveProperty('email');
216
+ expect(user).toHaveProperty('avatar');
217
+ expect(user).toHaveProperty('created');
218
+ expect(user).toHaveProperty('anonymousIds');
219
+ expect(user).toHaveProperty('sessionIds');
220
+ });
221
+
222
+ test('creates a user with a created date within the specified range', () => {
223
+ const numDays = 30;
224
+ const user = generateUser('uuid-123', numDays);
225
+ const createdDate = dayjs(user.created, 'YYYY-MM-DD');
226
+ expect(createdDate.isValid()).toBeTruthy();
227
+ expect(createdDate.isBefore(dayjs.unix(global.NOW))).toBeTruthy();
228
+ });
229
+ });
230
+
231
+
232
+
233
+ describe('enrich array', () => {
234
+ test('hook works', () => {
235
+ const arr = [];
236
+ const hook = (item) => item * 2;
237
+ const enrichedArray = enrichArray(arr, { hook });
238
+ enrichedArray.hookPush(1);
239
+ enrichedArray.hookPush(2);
240
+ expect(enrichedArray.includes(2)).toBeTruthy();
241
+ expect(enrichedArray.includes(4)).toBeTruthy();
242
+ });
243
+
244
+ test('filter empties', () => {
245
+ const arr = [];
246
+ const hook = (item) => item ? item.toString() : item;
247
+ const enrichedArray = enrichArray(arr, { hook });
248
+ enrichedArray.hookPush(null);
249
+ enrichedArray.hookPush(undefined);
250
+ enrichedArray.hookPush({});
251
+ enrichedArray.hookPush({ a: 1 });
252
+ enrichedArray.hookPush([1, 2]);
253
+ expect(enrichedArray).toHaveLength(3);
254
+ expect(enrichedArray.includes('null')).toBeFalsy();
255
+ expect(enrichedArray.includes('undefined')).toBeFalsy();
256
+ expect(enrichedArray.includes('[object Object]')).toBeTruthy();
257
+ expect(enrichedArray.includes('1')).toBeTruthy();
258
+ expect(enrichedArray.includes('2')).toBeTruthy();
259
+
260
+ });
261
+ });
27
262
 
28
- const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer, mapToRange, person, pick, range, weighList, weightedRange } = require('../utils');
29
263
 
30
264
  describe('utils', () => {
31
265
 
@@ -58,9 +292,9 @@ describe('utils', () => {
58
292
 
59
293
  test('person: fields', () => {
60
294
  const generatedPerson = person();
61
- expect(generatedPerson).toHaveProperty('$name');
62
- expect(generatedPerson).toHaveProperty('$email');
63
- expect(generatedPerson).toHaveProperty('$avatar');
295
+ expect(generatedPerson).toHaveProperty('name');
296
+ expect(generatedPerson).toHaveProperty('email');
297
+ expect(generatedPerson).toHaveProperty('avatar');
64
298
  });
65
299
 
66
300
 
@@ -73,7 +307,7 @@ describe('utils', () => {
73
307
  test('date: future', () => {
74
308
  const futureDate = date(10, false, 'YYYY-MM-DD')();
75
309
  expect(dayjs(futureDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
76
- expect(dayjs(futureDate).isAfter(dayjs())).toBeTruthy();
310
+ expect(dayjs(futureDate).isAfter(dayjs.unix(global.NOW))).toBeTruthy();
77
311
  });
78
312
 
79
313
  test('dates: pairs', () => {
@@ -107,7 +341,7 @@ describe('utils', () => {
107
341
  });
108
342
 
109
343
  test('weightedRange: within range', () => {
110
- const values = weightedRange(5, 15, 100);
344
+ const values = weightedRange(5, 15);
111
345
  expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
112
346
  expect(values.length).toBe(100);
113
347
  });
@@ -146,6 +380,19 @@ describe('utils', () => {
146
380
  });
147
381
 
148
382
 
383
+ test('times', () => {
384
+ const dates = [];
385
+ for (let i = 0; i < 10000; i++) {
386
+ const earliest = dayjs().subtract(u.rand(5, 50), 'D');
387
+ dates.push(TimeSoup());
388
+ }
389
+ const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
390
+ const badYear = dates.filter(d => !d.startsWith('202'));
391
+ expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
392
+ expect(dates.every(d => d.startsWith('202'))).toBe(true);
393
+
394
+ });
395
+
149
396
  test('date', () => {
150
397
  const result = date();
151
398
  expect(dayjs(result()).isValid()).toBe(true);
@@ -206,4 +453,89 @@ describe('utils', () => {
206
453
  });
207
454
 
208
455
 
456
+ test('weighArray: works', () => {
457
+ const arr = ['a', 'b', 'c'];
458
+ const weightedArr = weighArray(arr);
459
+ expect(weightedArr.length).toBeGreaterThanOrEqual(arr.length);
460
+ });
461
+
462
+ test('weighFunnels: works', () => {
463
+ const acc = [];
464
+ const funnel = { weight: 3 };
465
+ const result = weighFunnels(acc, funnel);
466
+ expect(result.length).toBe(3);
467
+ });
468
+
469
+ test('progress: outputs correctly', () => {
470
+ // @ts-ignore
471
+ const mockStdoutWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
472
+ progress('test', 50);
473
+ expect(mockStdoutWrite).toHaveBeenCalled();
474
+ mockStdoutWrite.mockRestore();
475
+ });
476
+
477
+ test('range: works', () => {
478
+ const result = [];
479
+ range.call(result, 1, 5);
480
+ expect(result).toEqual([1, 2, 3, 4, 5]);
481
+ });
482
+
483
+
484
+
485
+ test('shuffleArray: works', () => {
486
+ const arr = [1, 2, 3, 4, 5];
487
+ const shuffled = shuffleArray([...arr]);
488
+ expect(shuffled).not.toEqual(arr);
489
+ expect(shuffled.sort()).toEqual(arr.sort());
490
+ });
491
+
492
+ test('shuffleExceptFirst: works', () => {
493
+ const arr = [1, 2, 3, 4, 5];
494
+ const shuffled = shuffleExceptFirst([...arr]);
495
+ expect(shuffled[0]).toBe(arr[0]);
496
+ expect(shuffled.slice(1).sort()).toEqual(arr.slice(1).sort());
497
+ });
498
+
499
+ test('shuffleExceptLast: works', () => {
500
+ const arr = [1, 2, 3, 4, 5];
501
+ const shuffled = shuffleExceptLast([...arr]);
502
+ expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
503
+ expect(shuffled.slice(0, -1).sort()).toEqual(arr.slice(0, -1).sort());
504
+ });
505
+
506
+ test('fixFirstAndLast: works', () => {
507
+ const arr = [1, 2, 3, 4, 5];
508
+ const shuffled = fixFirstAndLast([...arr]);
509
+ expect(shuffled[0]).toBe(arr[0]);
510
+ expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
511
+ expect(shuffled.slice(1, -1).sort()).toEqual(arr.slice(1, -1).sort());
512
+ });
513
+
514
+ test('shuffleMiddle: works', () => {
515
+ const arr = [1, 2, 3, 4, 5];
516
+ const shuffled = shuffleMiddle([...arr]);
517
+ expect(shuffled[0]).toBe(arr[0]);
518
+ expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
519
+ expect(shuffled.slice(1, -1).sort()).toEqual(arr.slice(1, -1).sort());
520
+ });
521
+
522
+ test('shuffleOutside: works', () => {
523
+ const arr = [1, 2, 3, 4, 5];
524
+ const shuffled = shuffleOutside([...arr]);
525
+ expect(shuffled.slice(1, -1)).toEqual(arr.slice(1, -1));
526
+ });
527
+
528
+ test('box normal distribution', () => {
529
+ const values = [];
530
+ for (let i = 0; i < 10000; i++) {
531
+ values.push(boxMullerRandom());
532
+ }
533
+ const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
534
+ const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
535
+ const stdDev = Math.sqrt(variance);
536
+ expect(mean).toBeCloseTo(0, 1);
537
+ expect(stdDev).toBeCloseTo(1, 1);
538
+ });
539
+
540
+
209
541
  });
package/tmp/.gitkeep ADDED
File without changes
package/tsconfig.json CHANGED
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "include": [
13
13
  "**/*.js"
14
- ],
14
+ , "soupTemplates.mjs", "testSoup.mjs", "testCases.mjs" ],
15
15
  "exclude": [
16
16
  "node_modules"
17
17
  ]
package/types.d.ts CHANGED
@@ -9,16 +9,20 @@ declare namespace main {
9
9
  token?: string;
10
10
  seed?: string;
11
11
  numDays?: number;
12
+ epochStart?: number;
13
+ epochEnd?: number;
12
14
  numEvents?: number;
13
15
  numUsers?: number;
14
16
  format?: "csv" | "json";
15
17
  region?: "US" | "EU";
16
- events?: EventConfig[];
18
+ chance?: any;
19
+ events?: EventConfig[]; //can also be a array of strings
17
20
  superProps?: Record<string, ValueValid>;
21
+ funnels?: Funnel[];
18
22
  userProps?: Record<string, ValueValid>;
19
23
  scdProps?: Record<string, ValueValid>;
20
24
  mirrorProps?: Record<string, MirrorProps>;
21
- groupKeys?: [string, number][] | [string, number, string[]][];
25
+ groupKeys?: [string, number][] | [string, number, string[]][]; // [key, numGroups, [events]]
22
26
  groupProps?: Record<string, Record<string, ValueValid>>;
23
27
  lookupTables?: LookupTable[];
24
28
  writeToDisk?: boolean;
@@ -26,19 +30,37 @@ declare namespace main {
26
30
  verbose?: boolean;
27
31
  anonIds?: boolean;
28
32
  sessionIds?: boolean;
33
+ makeChart?: boolean | string;
34
+ soup?: soup;
29
35
  hook?: Hook<any>;
30
36
  }
31
37
 
32
- export type Hook<T> = (record: any, type: string, meta: any) => T;
38
+ type soup = {
39
+ deviation?: number;
40
+ peaks?: number;
41
+ mean?: number;
42
+ };
43
+
44
+ type hookTypes =
45
+ | "event"
46
+ | "user"
47
+ | "group"
48
+ | "lookup"
49
+ | "scd"
50
+ | "mirror"
51
+ | "funnel-pre"
52
+ | "funnel-post"
53
+ | "";
54
+ export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
33
55
 
34
56
  export interface EnrichArrayOptions<T> {
35
57
  hook?: Hook<T>;
36
- type?: string;
58
+ type?: hookTypes;
37
59
  [key: string]: any;
38
60
  }
39
61
 
40
62
  export interface EnrichedArray<T> extends Array<T> {
41
- hPush: (item: T) => number;
63
+ hookPush: (item: T) => number;
42
64
  }
43
65
 
44
66
  export interface EventConfig {
@@ -46,6 +68,33 @@ declare namespace main {
46
68
  weight?: number;
47
69
  properties?: Record<string, ValueValid>;
48
70
  isFirstEvent?: boolean;
71
+ relativeTimeMs?: number;
72
+ }
73
+
74
+ export interface EventSpec {
75
+ event: string;
76
+ time: string;
77
+ insert_id: string;
78
+ device_id?: string;
79
+ session_id?: string;
80
+ user_id?: string;
81
+ [key: string]: ValueValid;
82
+ }
83
+
84
+ export interface Funnel {
85
+ sequence: string[];
86
+ weight?: number;
87
+ isFirstFunnel?: boolean;
88
+ order?:
89
+ | "sequential"
90
+ | "first-fixed"
91
+ | "last-fixed"
92
+ | "random"
93
+ | "first-and-last-fixed"
94
+ | "middle-fixed";
95
+ conversionRate?: number;
96
+ timeToConvert?: number;
97
+ props?: Record<string, ValueValid>;
49
98
  }
50
99
 
51
100
  export interface MirrorProps {
@@ -59,7 +108,7 @@ declare namespace main {
59
108
  attributes: Record<string, ValueValid>;
60
109
  }
61
110
 
62
- export interface SCDTable {
111
+ export interface SCDTableRow {
63
112
  distinct_id: string;
64
113
  insertTime: string;
65
114
  startTime: string;
@@ -72,17 +121,17 @@ declare namespace main {
72
121
  scdTableData: any[];
73
122
  groupProfilesData: GroupProfilesData[];
74
123
  lookupTableData: LookupTableData[];
75
- import?: ImportResults;
124
+ importResults?: ImportResults;
76
125
  files?: string[];
77
126
  };
78
127
 
79
128
  export interface EventData {
80
129
  event: string;
81
- $source: string;
130
+ source: string;
82
131
  time: string;
83
- $device_id?: string;
84
- $session_id?: string;
85
- $user_id?: string;
132
+ device_id?: string;
133
+ session_id?: string;
134
+ user_id?: string;
86
135
  [key: string]: any;
87
136
  }
88
137
 
@@ -107,12 +156,22 @@ declare namespace main {
107
156
  bytes: number;
108
157
  }
109
158
  export interface Person {
110
- $name: string;
111
- $email: string;
112
- $avatar: string;
113
- $created: string | undefined;
159
+ name: string;
160
+ email: string;
161
+ avatar: string;
162
+ created: string | undefined;
114
163
  anonymousIds: string[];
115
164
  sessionIds: string[];
165
+ distinct_id?: string;
166
+ }
167
+
168
+ export interface UserProfile {
169
+ name?: string;
170
+ email?: string;
171
+ avatar?: string;
172
+ created: string | undefined;
173
+ distinct_id: string;
174
+ [key: string]: ValueValid;
116
175
  }
117
176
  }
118
177