@ak--47/dungeon-master 1.0.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.
Files changed (66) hide show
  1. package/README.md +518 -0
  2. package/dungeons/array-of-object-lookup-schema.json +327 -0
  3. package/dungeons/array-of-object-lookup.js +220 -0
  4. package/dungeons/ecommerce-schema.json +462 -0
  5. package/dungeons/ecommerce.js +447 -0
  6. package/dungeons/education-schema.json +2409 -0
  7. package/dungeons/education.js +768 -0
  8. package/dungeons/fintech-schema.json +14034 -0
  9. package/dungeons/fintech.js +696 -0
  10. package/dungeons/foobar-schema.json +403 -0
  11. package/dungeons/foobar.js +296 -0
  12. package/dungeons/food-delivery-schema.json +192 -0
  13. package/dungeons/food-delivery.js +602 -0
  14. package/dungeons/food-schema.json +1152 -0
  15. package/dungeons/food.js +754 -0
  16. package/dungeons/gaming-schema.json +1270 -0
  17. package/dungeons/gaming.js +508 -0
  18. package/dungeons/insurance-application-schema.json +204 -0
  19. package/dungeons/insurance-application.js +605 -0
  20. package/dungeons/media-schema.json +906 -0
  21. package/dungeons/media.js +790 -0
  22. package/dungeons/retention-cadence-schema.json +78 -0
  23. package/dungeons/retention-cadence.js +244 -0
  24. package/dungeons/rpg-schema.json +4526 -0
  25. package/dungeons/rpg.js +919 -0
  26. package/dungeons/sanity-schema.json +255 -0
  27. package/dungeons/sanity.js +152 -0
  28. package/dungeons/sass-schema.json +1291 -0
  29. package/dungeons/sass.js +795 -0
  30. package/dungeons/scd-schema.json +919 -0
  31. package/dungeons/scd.js +277 -0
  32. package/dungeons/simple-schema.json +608 -0
  33. package/dungeons/simple.js +285 -0
  34. package/dungeons/simplest-schema.json +1418 -0
  35. package/dungeons/simplest.js +392 -0
  36. package/dungeons/social-schema.json +1118 -0
  37. package/dungeons/social.js +686 -0
  38. package/dungeons/text-generation-schema.json +3096 -0
  39. package/dungeons/text-generation.js +812 -0
  40. package/index.js +567 -0
  41. package/lib/core/config-validator.js +395 -0
  42. package/lib/core/context.js +204 -0
  43. package/lib/core/dungeon-loader.js +337 -0
  44. package/lib/core/storage.js +379 -0
  45. package/lib/generators/adspend.js +132 -0
  46. package/lib/generators/events.js +271 -0
  47. package/lib/generators/funnels.js +407 -0
  48. package/lib/generators/mirror.js +167 -0
  49. package/lib/generators/product-lookup.js +262 -0
  50. package/lib/generators/product-names.js +195 -0
  51. package/lib/generators/profiles.js +93 -0
  52. package/lib/generators/scd.js +124 -0
  53. package/lib/generators/text.js +1192 -0
  54. package/lib/orchestrators/mixpanel-sender.js +266 -0
  55. package/lib/orchestrators/user-loop.js +335 -0
  56. package/lib/templates/abbreviated.d.ts +169 -0
  57. package/lib/templates/defaults.js +1405 -0
  58. package/lib/templates/phrases.js +2526 -0
  59. package/lib/templates/schema.d.ts +173 -0
  60. package/lib/templates/soup-presets.js +188 -0
  61. package/lib/utils/function-registry.js +302 -0
  62. package/lib/utils/json-evaluator.js +172 -0
  63. package/lib/utils/logger.js +34 -0
  64. package/lib/utils/utils.js +1490 -0
  65. package/package.json +89 -0
  66. package/types.d.ts +865 -0
package/README.md ADDED
@@ -0,0 +1,518 @@
1
+ # dungeon-master
2
+
3
+ generate realistic fake analytics data at scale. events, users, groups, funnels, SCDs, lookup tables, ad spend, mirror datasets, organic text, and more.
4
+
5
+ this is the best kind of test data: real fake data (really).
6
+
7
+ ## what is this
8
+
9
+ dungeon-master creates high-volume, semi-structured event data with deliberate patterns ("hooks") baked in. you define a "dungeon" (a configuration describing your data model), and the generator produces millions of events that look like real user behavior, because the time distributions, property weights, and behavioral patterns are all modeled from real-world analytics data.
10
+
11
+ the key piece is the **hook system**. hooks let you engineer specific, discoverable patterns into the generated data. things like "premium users convert 2x better" or "there was a service outage during days 40-47" or "users who watch low-quality video churn at 50%." the data looks organic, but you have the answer key.
12
+
13
+ this matters because it's really hard to build, test, and train analytics tools without realistic data that has known ground truth. and it's especially hard when you need millions of events with temporal patterns, funnel behaviors, and cross-table correlations.
14
+
15
+ i built this because i needed it. and after using it across hundreds of customer demos, internal testing, and AI training workflows... it just works.
16
+
17
+ ```bash
18
+ npm install @ak--47/dungeon-master
19
+ ```
20
+
21
+ ## quick start
22
+
23
+ ```javascript
24
+ import DUNGEON_MASTER from '@ak--47/dungeon-master';
25
+
26
+ // simple: pass a config object
27
+ const result = await DUNGEON_MASTER({
28
+ numUsers: 1_000,
29
+ numEvents: 100_000,
30
+ numDays: 90,
31
+ format: 'json',
32
+ writeToDisk: true,
33
+ events: [
34
+ { event: 'page view', weight: 10 },
35
+ { event: 'sign up', weight: 1, isFirstEvent: true },
36
+ { event: 'purchase', weight: 3, properties: { amount: [10, 25, 50, 100, 250] } }
37
+ ]
38
+ });
39
+
40
+ console.log(`${result.eventCount} events, ${result.userCount} users`);
41
+ ```
42
+
43
+ the main export accepts multiple input formats. use whatever fits your workflow:
44
+
45
+ ```javascript
46
+ // load a dungeon file from disk
47
+ const result = await DUNGEON_MASTER('./dungeons/simple.js');
48
+
49
+ // load a JSON dungeon (exported from the UI)
50
+ const result = await DUNGEON_MASTER('./dungeons/simple-schema.json');
51
+
52
+ // run multiple dungeons
53
+ const results = await DUNGEON_MASTER([
54
+ './dungeons/gaming.js',
55
+ './dungeons/media.js',
56
+ './dungeons/food-delivery.js'
57
+ ]);
58
+
59
+ // pass raw javascript as a string
60
+ const result = await DUNGEON_MASTER(`
61
+ export default {
62
+ numUsers: 500,
63
+ numEvents: 50_000,
64
+ numDays: 60,
65
+ events: [
66
+ { event: 'click', weight: 5 },
67
+ { event: 'submit', weight: 2 }
68
+ ]
69
+ };
70
+ `);
71
+
72
+ // override any config when loading from files
73
+ const result = await DUNGEON_MASTER('./dungeons/complex.js', {
74
+ numUsers: 100, // shrink for testing
75
+ writeToDisk: true,
76
+ verbose: true
77
+ });
78
+ ```
79
+
80
+ ## send data to mixpanel
81
+
82
+ pass a project token and the generated data imports directly:
83
+
84
+ ```javascript
85
+ const result = await DUNGEON_MASTER({
86
+ ...myDungeon,
87
+ token: process.env.MIXPANEL_TOKEN,
88
+ region: 'US'
89
+ });
90
+
91
+ console.log(result.importResults);
92
+ ```
93
+
94
+ ## dungeons
95
+
96
+ a dungeon is a javascript file that exports a configuration object. it defines your entire data model: events, funnels, user properties, group analytics, SCDs, and a hook function that engineers discoverable patterns into the data.
97
+
98
+ see `dungeons/` for examples ranging from simple (5 events, no hooks) to complex (18 events, 8 funnels, 8 hooks with temporal windowing, closure state, and cross-table correlation).
99
+
100
+ ```javascript
101
+ // dungeons/my-app.js
102
+ import dayjs from 'dayjs';
103
+ import { pickAWinner, weighNumRange, integer } from '@ak--47/dungeon-master/utils';
104
+
105
+ export default {
106
+ seed: 'my-app-v1',
107
+ numUsers: 10_000,
108
+ numEvents: 1_000_000,
109
+ numDays: 120,
110
+ format: 'json',
111
+
112
+ events: [
113
+ { event: 'page view', weight: 10, properties: { page: ['/', '/pricing', '/docs', '/blog'] } },
114
+ { event: 'sign up', weight: 1, isFirstEvent: true },
115
+ { event: 'feature used', weight: 8, properties: { feature: pickAWinner(['search', 'export', 'share', 'filter']) } },
116
+ { event: 'upgrade', weight: 2, properties: { plan: ['starter', 'pro', 'enterprise'], amount: weighNumRange(10, 500) } },
117
+ { event: 'support ticket', weight: 1, properties: { priority: ['low', 'medium', 'high', 'critical'] } }
118
+ ],
119
+
120
+ funnels: [
121
+ { sequence: ['page view', 'sign up'], conversionRate: 40, isFirstFunnel: true, order: 'sequential' },
122
+ { sequence: ['feature used', 'upgrade'], conversionRate: 15, order: 'sequential', timeToConvert: 72 }
123
+ ],
124
+
125
+ userProps: {
126
+ plan: ['free', 'free', 'free', 'starter', 'starter', 'pro', 'enterprise'],
127
+ company_size: weighNumRange(1, 500, 0.5)
128
+ },
129
+
130
+ soup: 'growth',
131
+
132
+ hook: function(record, type, meta) {
133
+ // enterprise users convert 3x better
134
+ if (type === 'funnel-pre' && meta.profile.plan === 'enterprise') {
135
+ record.conversionRate = Math.min(record.conversionRate * 3, 95);
136
+ }
137
+ return record;
138
+ }
139
+ };
140
+ ```
141
+
142
+ ### dungeon utilities
143
+
144
+ dungeon scripts can import utilities from the package directly:
145
+
146
+ ```javascript
147
+ // weighted random selection, number ranges, date generation, and more
148
+ import { pickAWinner, weighNumRange, weighChoices, date, integer } from '@ak--47/dungeon-master/utils';
149
+
150
+ // organic text generation (support tickets, reviews, search queries, etc.)
151
+ import { createTextGenerator, generateBatch } from '@ak--47/dungeon-master/text';
152
+ ```
153
+
154
+ these are the same functions used internally. `pickAWinner` creates weighted distributions, `weighNumRange` generates realistic numeric ranges with configurable skew, and the text generators produce organic-looking strings with sentiment analysis and keyword injection.
155
+
156
+ ## the hook system
157
+
158
+ hooks are the most important feature. a hook is a single function on your dungeon config that receives every piece of data as it flows through the pipeline. you can mutate events, modify conversion rates, inject synthetic events, simulate churn, engineer temporal patterns, and correlate behaviors across tables.
159
+
160
+ ```javascript
161
+ hook: function(record, type, meta) {
162
+ // type tells you what kind of data you're looking at
163
+ // record is the data object (event, profile, or array of events)
164
+ // meta has contextual info (user profile, config, etc.)
165
+
166
+ return record;
167
+ }
168
+ ```
169
+
170
+ ### hook types
171
+
172
+ hooks fire in this order for each user:
173
+
174
+ | type | what you get | what you can do |
175
+ |------|-------------|----------------|
176
+ | `"user"` | user profile object | set computed properties, segments, tiers |
177
+ | `"scd-pre"` | SCD entries array | modify time-series attribute mutations |
178
+ | `"funnel-pre"` | funnel config + `meta.profile` | change conversion rates based on user properties |
179
+ | `"event"` | single event (flat props) | modify properties, tag events, rename events |
180
+ | `"funnel-post"` | array of funnel events | splice extra events between funnel steps |
181
+ | `"everything"` | ALL events for one user | correlate across event types, filter/inject/duplicate events |
182
+
183
+ storage hooks (`"ad-spend"`, `"group"`, `"mirror"`, `"lookup"`) fire during write, not generation.
184
+
185
+ ### hook patterns
186
+
187
+ these are the patterns i use most. they cover probably 90% of what you'd want to engineer into test data:
188
+
189
+ **temporal windowing** (simulate a product launch, outage, or improvement):
190
+
191
+ ```javascript
192
+ hook: function(record, type, meta) {
193
+ if (type === 'event') {
194
+ const LAUNCH_DAY = dayjs().subtract(30, 'day');
195
+ if (dayjs(record.time).isAfter(LAUNCH_DAY)) {
196
+ if (record.event === 'purchase') record.amount *= 2;
197
+ }
198
+ }
199
+ return record;
200
+ }
201
+ ```
202
+
203
+ **user-property-driven conversion** (premium users convert better):
204
+
205
+ ```javascript
206
+ hook: function(record, type, meta) {
207
+ if (type === 'funnel-pre') {
208
+ if (meta.profile.plan === 'enterprise') {
209
+ record.conversionRate = Math.min(record.conversionRate * 2.5, 95);
210
+ }
211
+ }
212
+ return record;
213
+ }
214
+ ```
215
+
216
+ **two-pass behavioral tagging** (identify power users, then tag all their events):
217
+
218
+ ```javascript
219
+ hook: function(record, type, meta) {
220
+ if (type === 'everything') {
221
+ const purchases = record.filter(e => e.event === 'purchase');
222
+ const isPowerUser = purchases.length > 5;
223
+ for (const event of record) {
224
+ event.is_power_user = isPowerUser;
225
+ }
226
+ }
227
+ return record;
228
+ }
229
+ ```
230
+
231
+ **simulating churn** (low-quality users drop off):
232
+
233
+ ```javascript
234
+ hook: function(record, type, meta) {
235
+ if (type === 'everything') {
236
+ const lowQuality = record.filter(e => e.quality === '240p').length;
237
+ const highQuality = record.filter(e => e.quality === '1080p').length;
238
+ if (lowQuality > highQuality && chance.bool({ likelihood: 50 })) {
239
+ const midpoint = Math.floor(record.length / 2);
240
+ return record.slice(0, midpoint); // user "churns" halfway through
241
+ }
242
+ }
243
+ return record;
244
+ }
245
+ ```
246
+
247
+ **event injection** (add synthetic milestone events):
248
+
249
+ ```javascript
250
+ hook: function(record, type, meta) {
251
+ if (type === 'everything') {
252
+ const purchases = record.filter(e => e.event === 'purchase');
253
+ if (purchases.length >= 10) {
254
+ record.push({
255
+ event: 'loyalty milestone',
256
+ time: purchases[9].time,
257
+ user_id: purchases[9].user_id,
258
+ milestone: '10th purchase',
259
+ total_spend: purchases.reduce((sum, p) => sum + (p.amount || 0), 0)
260
+ });
261
+ }
262
+ }
263
+ return record;
264
+ }
265
+ ```
266
+
267
+ ### critical hook rules
268
+
269
+ 1. event properties are **flat** on the record: `record.amount`, not `record.properties.amount`
270
+ 2. injected events need `user_id` (not `distinct_id`) and a valid `time` string
271
+ 3. use `dayjs` for time operations inside hooks
272
+ 4. to drop events, use the `everything` hook and return a filtered array. don't return `{}` from event hooks (creates broken events)
273
+ 5. the `everything` hook is the most powerful. it sees all events for one user, has access to `meta.profile`, and can correlate across event types
274
+
275
+ ## timesoup
276
+
277
+ timesoup controls how events are distributed across time. it uses gaussian cluster sampling layered with day-of-week and hour-of-day weighting derived from... i won't tell you. a prize goes to whoever can guess. the result is realistic temporal patterns: weekday peaks, weekend valleys, morning surges, afternoon dips.
278
+
279
+ ### presets
280
+
281
+ ```javascript
282
+ soup: 'growth' // default. gradual uptrend with weekly cycles
283
+ ```
284
+
285
+ | preset | pattern | use case |
286
+ |--------|---------|----------|
287
+ | `"steady"` | flat, minimal variation | mature saas, utility apps |
288
+ | `"growth"` | gradual uptrend + weekly cycle | general purpose (default) |
289
+ | `"spiky"` | dramatic peaks and valleys | gaming, social, viral products |
290
+ | `"seasonal"` | 3-4 major waves | ecommerce, education |
291
+ | `"global"` | flat DOW + flat HOD | global saas, infrastructure |
292
+ | `"churny"` | flat, no growth trend | declining products (pair with churn hooks) |
293
+ | `"chaotic"` | wild variation | anomaly detection, incident response |
294
+
295
+ ### custom configuration
296
+
297
+ ```javascript
298
+ // preset with overrides
299
+ soup: { preset: 'spiky', deviation: 5 }
300
+
301
+ // fully custom
302
+ soup: {
303
+ peaks: 200,
304
+ deviation: 2,
305
+ mean: 0,
306
+ dayOfWeekWeights: [0.637, 1.0, 0.999, 0.998, 0.966, 0.802, 0.528], // [Sun..Sat]
307
+ hourOfDayWeights: [/* 24 values, index 0 = midnight UTC */]
308
+ }
309
+ ```
310
+ ^ be warned, this can blow up your dataset. most of the 'growth' is what you want (right?)
311
+
312
+ ## output formats
313
+
314
+ generated data writes to `./data/` by default, but you can pass a path to `writeToDisk` and it will write there instead. supported formats:
315
+
316
+ | format | extension | notes |
317
+ |--------|-----------|-------|
318
+ | `json` | `.json` | newline-delimited JSON (one object per line) |
319
+ | `csv` | `.csv` | standard CSV with headers |
320
+ | `parquet` | `.parquet` | columnar format via hyparquet-writer |
321
+
322
+ all formats support gzip compression (`gzip: true`).
323
+
324
+ ```javascript
325
+ {
326
+ format: 'parquet',
327
+ writeToDisk: true,
328
+ gzip: true
329
+ }
330
+ ```
331
+
332
+ ### cloud storage
333
+
334
+ you can write directly to google cloud storage by using a `gs://` path:
335
+
336
+ ```javascript
337
+ {
338
+ writeToDisk: 'gs://my-bucket/datasets/gaming/',
339
+ format: 'json'
340
+ }
341
+ ```
342
+
343
+ ## OOM protection and batch mode
344
+
345
+ large datasets (2M+ events) automatically enable batch mode, which flushes data to disk in chunks to prevent out-of-memory crashes. you can configure this manually:
346
+
347
+ ```javascript
348
+ {
349
+ numEvents: 50_000_000,
350
+ batchSize: 1_000_000, // flush every 1M records
351
+ writeToDisk: true,
352
+ format: 'csv'
353
+ }
354
+ ```
355
+
356
+ batch mode writes numbered files (`dataset-EVENTS-001.csv`, `dataset-EVENTS-002.csv`, etc.) and streams data through instead of holding everything in memory. this means you can generate datasets of arbitrary size on a machine with limited RAM.
357
+
358
+ ## data model
359
+
360
+ dungeon-master generates multiple data types that mirror a real analytics implementation:
361
+
362
+ | data type | config key | description |
363
+ |-----------|-----------|-------------|
364
+ | events | `events` | timestamped user actions with arbitrary properties |
365
+ | user profiles | `userProps` | per-user attributes (plan, company, preferences) |
366
+ | super properties | `superProps` | properties attached to every event (theme, platform) |
367
+ | funnels | `funnels` | conversion sequences with configurable rates and ordering |
368
+ | group profiles | `groupKeys` + `groupProps` | B2B group analytics (companies, teams) |
369
+ | SCDs | `scdProps` | slowly changing dimensions (subscription tier over time) |
370
+ | lookup tables | `lookupTables` | dimension tables (product catalog, region mapping) |
371
+ | ad spend | `hasAdSpend` | daily ad spend with impressions, clicks, cost metrics |
372
+ | mirror datasets | `mirrorProps` | transformed copies of event data (A/B versions) |
373
+ | organic text | `createTextGenerator` | reviews, support tickets, search queries, chat messages |
374
+
375
+ ## funnels
376
+
377
+ funnels define conversion sequences. users enter a funnel, and at each step some percentage drops off. the ordering strategy controls how events within the funnel are sequenced:
378
+
379
+ ```javascript
380
+ funnels: [
381
+ {
382
+ sequence: ['page view', 'sign up', 'onboarding', 'first action'],
383
+ conversionRate: 35,
384
+ order: 'sequential', // strict left-to-right ordering
385
+ timeToConvert: 24, // hours between steps
386
+ isFirstFunnel: true, // this is the entry funnel
387
+ experiment: true // generates A/B/C variants automatically
388
+ },
389
+ {
390
+ sequence: ['view item', 'add to cart', 'checkout'],
391
+ conversionRate: 20,
392
+ order: 'first-and-last-fixed', // first and last steps are fixed, middle shuffled
393
+ timeToConvert: 48,
394
+ props: { source: 'organic' }, // constant props on all funnel events
395
+ bindPropsIndex: 1 // props bind at step 1 and persist through
396
+ }
397
+ ]
398
+ ```
399
+
400
+ ordering strategies: `sequential`, `random`, `first-fixed`, `last-fixed`, `first-and-last-fixed`, `middle-fixed`, `interrupted`
401
+
402
+ ## user generation
403
+
404
+ users are generated with configurable birth distributions. `percentUsersBornInDataset` controls how many users were "created" during the dataset window vs. pre-existing. `bornRecentBias` skews new user creation toward recent dates (0 = uniform, 1 = heavily recent).
405
+
406
+ ```javascript
407
+ {
408
+ numUsers: 10_000,
409
+ percentUsersBornInDataset: 25, // 25% of users sign up during the time window
410
+ bornRecentBias: 0.5 // new users skew toward recent dates
411
+ }
412
+ ```
413
+
414
+ ## seeded generation
415
+
416
+ all randomness is seeded. same seed + same config + concurrency=1 = identical output every time:
417
+
418
+ ```javascript
419
+ {
420
+ seed: 'my-reproducible-dataset',
421
+ concurrency: 1
422
+ }
423
+ ```
424
+
425
+ ## what gets generated
426
+
427
+ the result object contains everything:
428
+
429
+ ```javascript
430
+ const result = await DUNGEON_MASTER(config);
431
+
432
+ result.eventData // all generated events
433
+ result.userProfilesData // user profiles
434
+ result.scdTableData // SCD mutations
435
+ result.groupProfilesData // group profiles
436
+ result.adSpendData // ad spend data
437
+ result.lookupTableData // lookup table entries
438
+ result.mirrorEventData // mirror dataset
439
+
440
+ result.eventCount // total event count
441
+ result.userCount // total user count
442
+ result.files // written file paths (if writeToDisk)
443
+ result.time // { start, end, delta, human }
444
+ result.importResults // mixpanel import results (if token provided)
445
+ ```
446
+
447
+ ## text generation
448
+
449
+ dungeon-master includes a built-in text generator for creating organic-looking strings (support tickets, product reviews, search queries, chat messages, etc.) with configurable sentiment, style, and keyword injection:
450
+
451
+ ```javascript
452
+ import { createTextGenerator, generateBatch } from '@ak--47/dungeon-master/text';
453
+
454
+ const generator = createTextGenerator({
455
+ style: 'review',
456
+ tone: 'pos',
457
+ keywords: { products: ['AcmeWidget', 'ProPlan'], features: ['dashboard', 'API'] },
458
+ keywordDensity: 0.3,
459
+ typos: true,
460
+ typoRate: 0.02
461
+ });
462
+
463
+ const reviews = generator.generateBatch({ n: 1000, returnType: 'objects' });
464
+ ```
465
+
466
+ styles: `support`, `review`, `search`, `feedback`, `chat`, `email`, `forum`, `comments`, `tweet`
467
+
468
+ ## scripts
469
+
470
+ ```bash
471
+ npm run dungeon:run # run a dungeon file locally
472
+ npm run dungeon:to-json # convert JS dungeon to JSON (for UI import)
473
+ npm run dungeon:from-json # convert JSON to JS dungeon
474
+ npm test # vitest test suite
475
+ npm run typecheck # typescript check
476
+ ```
477
+
478
+ ## config reference
479
+
480
+ see [types.d.ts](types.d.ts) for the complete `Dungeon` interface. here are the most commonly used properties:
481
+
482
+ | property | type | default | description |
483
+ |----------|------|---------|-------------|
484
+ | `numUsers` | number | 1000 | number of users to generate |
485
+ | `numEvents` | number | 100000 | target event count |
486
+ | `numDays` | number | 30 | days the dataset spans |
487
+ | `seed` | string | random | RNG seed for reproducibility |
488
+ | `format` | string | `'csv'` | output format (csv, json, parquet) |
489
+ | `token` | string | null | mixpanel project token (triggers import) |
490
+ | `region` | string | `'US'` | mixpanel data residency |
491
+ | `writeToDisk` | boolean/string | false | write files to ./data/ or a gs:// path |
492
+ | `gzip` | boolean | false | compress output files |
493
+ | `verbose` | boolean | false | print progress |
494
+ | `strictEventCount` | boolean | false | stop at exact numEvents |
495
+ | `batchSize` | number | 2500000 | records before auto-flush |
496
+ | `concurrency` | number | 1 | parallel user generation |
497
+ | `soup` | string/object | `'growth'` | time distribution preset |
498
+ | `bornRecentBias` | number | 0.3 | user birth date skew (0-1) |
499
+ | `percentUsersBornInDataset` | number | 15 | % of users born in time window |
500
+ | `hook` | function/string | passthrough | data transformation function |
501
+ | `hasLocation` | boolean | false | include geo properties |
502
+ | `hasCampaigns` | boolean | false | include UTM properties |
503
+ | `hasAdSpend` | boolean | false | generate ad spend data |
504
+ | `hasAnonIds` | boolean | false | generate anonymous IDs |
505
+ | `hasSessionIds` | boolean | false | generate session IDs |
506
+ | `alsoInferFunnels` | boolean | false | auto-generate funnels from events |
507
+
508
+ ## why
509
+
510
+ i'm building a system that teaches LLMs how to find insights in high-volume, noisy, semi-structured event data. to do that, you need datasets where the patterns are known, complex, and realistic. dungeon-master is that dataset generator.
511
+
512
+ it's the very best kind of test data: the kind where you have the answer key. you know exactly what patterns are in there because you engineered them. and the noise around those patterns is realistic because the time distributions, property weights, and user behaviors are modeled from real analytics data.
513
+
514
+ pretty much any engineer who works with product analytics, data pipelines, or AI/ML would find this useful. it's also great for demos, load testing, and integration testing against any system that ingests event data.
515
+
516
+ ## contributing
517
+
518
+ contributions welcome. for issues or feature requests: [github.com/ak--47/dungeon-master/issues](https://github.com/ak--47/dungeon-master/issues)