make-mp-data 1.2.21 → 1.3.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.
- package/.vscode/launch.json +13 -0
- package/.vscode/settings.json +19 -1
- package/README.md +71 -18
- package/index.js +183 -58
- package/models/complex.js +183 -44
- package/models/deepNest.js +13 -13
- package/models/simple.js +47 -25
- package/package.json +7 -5
- package/tests/e2e.test.js +20 -10
- package/tests/unit.test.js +53 -29
- package/types.d.ts +111 -79
- package/utils.js +24 -60
package/tests/unit.test.js
CHANGED
|
@@ -55,38 +55,28 @@ describe('utils', () => {
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
test('hashtags', () => {
|
|
59
|
-
const hashtags = makeHashTags();
|
|
60
|
-
expect(hashtags).toBeInstanceOf(Array);
|
|
61
|
-
expect(hashtags).not.toHaveLength(0);
|
|
62
|
-
hashtags.forEach(tag => {
|
|
63
|
-
expect(tag).toMatch(/^#/);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
|
|
68
58
|
|
|
69
59
|
test('person: fields', () => {
|
|
70
60
|
const generatedPerson = person();
|
|
71
61
|
expect(generatedPerson).toHaveProperty('$name');
|
|
72
62
|
expect(generatedPerson).toHaveProperty('$email');
|
|
73
|
-
expect(generatedPerson).toHaveProperty('$avatar');
|
|
63
|
+
expect(generatedPerson).toHaveProperty('$avatar');
|
|
74
64
|
});
|
|
75
65
|
|
|
76
66
|
|
|
77
|
-
test('date: past
|
|
67
|
+
test('date: past', () => {
|
|
78
68
|
const pastDate = date(10, true, 'YYYY-MM-DD')();
|
|
79
69
|
expect(dayjs(pastDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
|
|
80
70
|
expect(dayjs(pastDate).isBefore(dayjs())).toBeTruthy();
|
|
81
71
|
});
|
|
82
72
|
|
|
83
|
-
test('date: future
|
|
73
|
+
test('date: future', () => {
|
|
84
74
|
const futureDate = date(10, false, 'YYYY-MM-DD')();
|
|
85
75
|
expect(dayjs(futureDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
|
|
86
76
|
expect(dayjs(futureDate).isAfter(dayjs())).toBeTruthy();
|
|
87
77
|
});
|
|
88
78
|
|
|
89
|
-
test('dates:
|
|
79
|
+
test('dates: pairs', () => {
|
|
90
80
|
const datePairs = dates(10, 3, 'YYYY-MM-DD');
|
|
91
81
|
expect(datePairs).toBeInstanceOf(Array);
|
|
92
82
|
expect(datePairs).toHaveLength(3);
|
|
@@ -95,18 +85,50 @@ describe('utils', () => {
|
|
|
95
85
|
});
|
|
96
86
|
});
|
|
97
87
|
|
|
98
|
-
test('choose:
|
|
88
|
+
test('choose: array', () => {
|
|
99
89
|
const options = ['apple', 'banana', 'cherry'];
|
|
100
90
|
const choice = choose(options);
|
|
101
91
|
expect(options).toContain(choice);
|
|
102
92
|
});
|
|
103
93
|
|
|
104
|
-
test('choose:
|
|
94
|
+
test('choose: function', () => {
|
|
105
95
|
const result = choose(() => 'test');
|
|
106
96
|
expect(result).toBe('test');
|
|
107
97
|
});
|
|
108
98
|
|
|
109
|
-
test('
|
|
99
|
+
test('choose: non-function / non-array', () => {
|
|
100
|
+
expect(choose('test')).toBe('test');
|
|
101
|
+
expect(choose(123)).toBe(123);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('choose: nested functions', () => {
|
|
105
|
+
const result = choose(() => () => () => 'nested');
|
|
106
|
+
expect(result).toBe('nested');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('weightedRange: within range', () => {
|
|
110
|
+
const values = weightedRange(5, 15, 100);
|
|
111
|
+
expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
|
|
112
|
+
expect(values.length).toBe(100);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('applySkew: skews', () => {
|
|
116
|
+
const value = boxMullerRandom();
|
|
117
|
+
const skewedValue = applySkew(value, .25);
|
|
118
|
+
expect(Math.abs(skewedValue)).toBeGreaterThanOrEqual(Math.abs(value));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('mapToRange: works', () => {
|
|
122
|
+
const value = 0;
|
|
123
|
+
const mean = 10;
|
|
124
|
+
const sd = 5;
|
|
125
|
+
const mappedValue = mapToRange(value, mean, sd);
|
|
126
|
+
expect(mappedValue).toBe(10);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
test('exhaust: elements', () => {
|
|
110
132
|
const arr = [1, 2, 3];
|
|
111
133
|
const exhaustFn = exhaust([...arr]);
|
|
112
134
|
expect(exhaustFn()).toBe(1);
|
|
@@ -115,25 +137,21 @@ describe('utils', () => {
|
|
|
115
137
|
expect(exhaustFn()).toBeUndefined();
|
|
116
138
|
});
|
|
117
139
|
|
|
118
|
-
test('generateEmoji: returns string of emojis', () => {
|
|
119
|
-
const emojis = generateEmoji(5)();
|
|
120
|
-
expect(typeof emojis).toBe('string');
|
|
121
|
-
expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
|
|
122
|
-
});
|
|
123
140
|
|
|
124
|
-
|
|
141
|
+
|
|
142
|
+
test('unique keys', () => {
|
|
125
143
|
const objects = [{ a: 1, b: 2 }, { a: 3, c: 4 }, { a: 5, b: 6 }];
|
|
126
144
|
const uniqueKeys = getUniqueKeys(objects);
|
|
127
145
|
expect(uniqueKeys).toEqual(expect.arrayContaining(['a', 'b', 'c']));
|
|
128
146
|
});
|
|
129
147
|
|
|
130
148
|
|
|
131
|
-
test('date
|
|
149
|
+
test('date', () => {
|
|
132
150
|
const result = date();
|
|
133
151
|
expect(dayjs(result()).isValid()).toBe(true);
|
|
134
152
|
});
|
|
135
153
|
|
|
136
|
-
test('dates
|
|
154
|
+
test('dates', () => {
|
|
137
155
|
const result = dates();
|
|
138
156
|
expect(result).toBeInstanceOf(Array);
|
|
139
157
|
expect(result.length).toBe(5); // Assuming default numPairs is 5
|
|
@@ -145,7 +163,7 @@ describe('utils', () => {
|
|
|
145
163
|
});
|
|
146
164
|
});
|
|
147
165
|
|
|
148
|
-
test('day
|
|
166
|
+
test('day', () => {
|
|
149
167
|
const start = '2020-01-01';
|
|
150
168
|
const end = '2020-01-30';
|
|
151
169
|
const result = day(start, end);
|
|
@@ -154,7 +172,7 @@ describe('utils', () => {
|
|
|
154
172
|
expect(dayjs(dayResult.day).isBefore(dayjs(dayResult.end))).toBe(true);
|
|
155
173
|
});
|
|
156
174
|
|
|
157
|
-
test('exhaust
|
|
175
|
+
test('exhaust', () => {
|
|
158
176
|
const arr = [1, 2, 3];
|
|
159
177
|
const next = exhaust(arr);
|
|
160
178
|
expect(next()).toBe(1);
|
|
@@ -163,13 +181,19 @@ describe('utils', () => {
|
|
|
163
181
|
expect(next()).toBe(undefined); // or whatever your implementation does after array is exhausted
|
|
164
182
|
});
|
|
165
183
|
|
|
166
|
-
test('
|
|
184
|
+
test('emoji: works', () => {
|
|
185
|
+
const emojis = generateEmoji(5)();
|
|
186
|
+
expect(typeof emojis).toBe('string');
|
|
187
|
+
expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('emoji: length', () => {
|
|
167
191
|
const result = generateEmoji();
|
|
168
192
|
const emojis = result();
|
|
169
193
|
expect(typeof emojis).toBe('string');
|
|
170
194
|
const emojiArray = emojis.split(', ');
|
|
171
195
|
expect(emojiArray.length).toBeLessThanOrEqual(10); // Assuming max default is 10
|
|
172
|
-
|
|
196
|
+
|
|
173
197
|
});
|
|
174
198
|
|
|
175
199
|
|
package/types.d.ts
CHANGED
|
@@ -1,82 +1,114 @@
|
|
|
1
1
|
declare namespace main {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
interface
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
}
|
|
102
|
+
}
|
|
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
|
+
|
|
81
113
|
export = main;
|
|
82
114
|
|
package/utils.js
CHANGED
|
@@ -92,10 +92,15 @@ function choose(value) {
|
|
|
92
92
|
return chance.pickone(value);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
if (typeof value === 'string') {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
95
99
|
// If it's not a function or array, return it as is
|
|
96
100
|
return value;
|
|
97
101
|
}
|
|
98
102
|
catch (e) {
|
|
103
|
+
console.error(`\n\nerror on value: ${value};\n\n`,e, '\n\n');
|
|
99
104
|
return '';
|
|
100
105
|
}
|
|
101
106
|
}
|
|
@@ -128,63 +133,6 @@ function integer(min, max) {
|
|
|
128
133
|
return 0;
|
|
129
134
|
};
|
|
130
135
|
|
|
131
|
-
function makeHashTags() {
|
|
132
|
-
const popularHashtags = [
|
|
133
|
-
'#GalacticAdventures',
|
|
134
|
-
'#EnchantedExplorations',
|
|
135
|
-
'#MagicalMoments',
|
|
136
|
-
'#EpicQuests',
|
|
137
|
-
'#WonderfulWorlds',
|
|
138
|
-
'#FantasyFrenzy',
|
|
139
|
-
'#MysticalMayhem',
|
|
140
|
-
'#MythicalMarvels',
|
|
141
|
-
'#LegendaryLegends',
|
|
142
|
-
'#DreamlandDiaries',
|
|
143
|
-
'#WhimsicalWonders',
|
|
144
|
-
'#FabledFables'
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
const numHashtags = integer(integer(1, 5), integer(5, 10));
|
|
148
|
-
const hashtags = [];
|
|
149
|
-
for (let i = 0; i < numHashtags; i++) {
|
|
150
|
-
hashtags.push(chance.pickone(popularHashtags));
|
|
151
|
-
}
|
|
152
|
-
return hashtags;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
function makeProducts() {
|
|
156
|
-
let categories = ["Device Accessories", "eBooks", "Automotive", "Baby Products", "Beauty", "Books", "Camera & Photo", "Cell Phones & Accessories", "Collectible Coins", "Consumer Electronics", "Entertainment Collectibles", "Fine Art", "Grocery & Gourmet Food", "Health & Personal Care", "Home & Garden", "Independent Design", "Industrial & Scientific", "Accessories", "Major Appliances", "Music", "Musical Instruments", "Office Products", "Outdoors", "Personal Computers", "Pet Supplies", "Software", "Sports", "Sports Collectibles", "Tools & Home Improvement", "Toys & Games", "Video, DVD & Blu-ray", "Video Games", "Watches"];
|
|
157
|
-
let slugs = ['/sale/', '/featured/', '/home/', '/search/', '/wishlist/', '/'];
|
|
158
|
-
let assetExtension = ['.png', '.jpg', '.jpeg', '.heic', '.mp4', '.mov', '.avi'];
|
|
159
|
-
let data = [];
|
|
160
|
-
let numOfItems = integer(1, 12);
|
|
161
|
-
|
|
162
|
-
for (var i = 0; i < numOfItems; i++) {
|
|
163
|
-
|
|
164
|
-
let category = chance.pickone(categories);
|
|
165
|
-
let slug = chance.pickone(slugs);
|
|
166
|
-
let asset = chance.pickone(assetExtension);
|
|
167
|
-
let product_id = chance.guid();
|
|
168
|
-
let price = integer(1, 300);
|
|
169
|
-
let quantity = integer(1, 5);
|
|
170
|
-
|
|
171
|
-
let item = {
|
|
172
|
-
product_id: product_id,
|
|
173
|
-
sku: integer(11111, 99999),
|
|
174
|
-
amount: price,
|
|
175
|
-
quantity: quantity,
|
|
176
|
-
value: price * quantity,
|
|
177
|
-
featured: chance.pickone([true, false]),
|
|
178
|
-
category: category,
|
|
179
|
-
urlSlug: slug + category,
|
|
180
|
-
asset: `${category}-${integer(1, 20)}${asset}`
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
data.push(item);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return data;
|
|
187
|
-
};
|
|
188
136
|
|
|
189
137
|
// Box-Muller transform to generate standard normally distributed values
|
|
190
138
|
function boxMullerRandom() {
|
|
@@ -207,7 +155,7 @@ function mapToRange(value, mean, sd) {
|
|
|
207
155
|
return Math.round(value * sd + mean);
|
|
208
156
|
};
|
|
209
157
|
|
|
210
|
-
function
|
|
158
|
+
function unOptimizedWeightedRange(min, max, size = 100, skew = 1) {
|
|
211
159
|
const mean = (max + min) / 2;
|
|
212
160
|
const sd = (max - min) / 4;
|
|
213
161
|
let array = [];
|
|
@@ -228,6 +176,23 @@ function weightedRange(min, max, size = 100, skew = 1) {
|
|
|
228
176
|
return array;
|
|
229
177
|
};
|
|
230
178
|
|
|
179
|
+
// optimized weighted range
|
|
180
|
+
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;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
231
196
|
function progress(thing, p) {
|
|
232
197
|
readline.cursorTo(process.stdout, 0);
|
|
233
198
|
process.stdout.write(`${thing} processed ... ${comma(p)}`);
|
|
@@ -359,8 +324,7 @@ module.exports = {
|
|
|
359
324
|
choose,
|
|
360
325
|
exhaust,
|
|
361
326
|
integer,
|
|
362
|
-
|
|
363
|
-
makeProducts,
|
|
327
|
+
|
|
364
328
|
boxMullerRandom,
|
|
365
329
|
applySkew,
|
|
366
330
|
mapToRange,
|