@ak--47/dungeon-master 1.2.2 → 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.
Files changed (52) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -2
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +2 -2
  16. package/dungeons/technical/simplest.js +2 -2
  17. package/dungeons/technical/text-generation.js +2 -2
  18. package/dungeons/vertical/ai-platform-schema.json +617 -0
  19. package/dungeons/vertical/ai-platform.js +799 -0
  20. package/dungeons/vertical/community.js +40 -26
  21. package/dungeons/vertical/crypto-schema.json +546 -0
  22. package/dungeons/vertical/crypto.js +721 -0
  23. package/dungeons/vertical/dating-schema.json +401 -0
  24. package/dungeons/vertical/dating.js +798 -0
  25. package/dungeons/vertical/devtools.js +13 -9
  26. package/dungeons/vertical/ecommerce.js +2 -3
  27. package/dungeons/vertical/education.js +4 -5
  28. package/dungeons/vertical/fintech.js +32 -29
  29. package/dungeons/vertical/fitness.js +2 -3
  30. package/dungeons/vertical/food-delivery.js +37 -43
  31. package/dungeons/vertical/gaming-schema.json +2495 -230
  32. package/dungeons/vertical/gaming.js +771 -388
  33. package/dungeons/vertical/healthcare.js +2 -3
  34. package/dungeons/vertical/insurance-application.js +2 -3
  35. package/dungeons/vertical/logistics.js +20 -14
  36. package/dungeons/vertical/marketplace.js +22 -14
  37. package/dungeons/vertical/media.js +39 -30
  38. package/dungeons/vertical/real-estate-schema.json +527 -0
  39. package/dungeons/vertical/real-estate.js +774 -0
  40. package/dungeons/vertical/sass.js +2 -3
  41. package/dungeons/vertical/social.js +15 -13
  42. package/dungeons/vertical/travel.js +59 -26
  43. package/lib/core/config-validator.js +71 -15
  44. package/lib/core/storage.js +14 -4
  45. package/lib/orchestrators/user-loop.js +39 -5
  46. package/lib/templates/macro-presets.js +111 -0
  47. package/lib/templates/soup-presets.js +19 -36
  48. package/package.json +8 -2
  49. package/types.d.ts +219 -42
  50. package/dungeons/user/.gitkeep +0 -0
  51. package/dungeons/vertical/rpg-schema.json +0 -2491
  52. package/dungeons/vertical/rpg.js +0 -976
@@ -1,6 +1,13 @@
1
1
  /**
2
- * TimeSoup preset configurations
3
- * Each preset defines time distribution parameters that produce distinct patterns.
2
+ * Soup preset configurations.
3
+ *
4
+ * Soup presets shape the INTRA-WEEK and INTRA-DAY rhythm: how many Gaussian
5
+ * peaks to sample from, how tightly each clusters, and how to weight each
6
+ * day-of-week and hour-of-day during accept/reject sampling.
7
+ *
8
+ * Soup is the orthogonal axis to macro presets (lib/templates/macro-presets.js).
9
+ * Macro shapes the big-picture trend (births, growth, decline). Soup shapes
10
+ * the texture inside that trend (weekly cycle, daily peak, burstiness).
4
11
  *
5
12
  * Parameters:
6
13
  * - peaks(numDays): function returning number of Gaussian clusters
@@ -8,9 +15,6 @@
8
15
  * - mean: offset from chunk center (0 = centered)
9
16
  * - dayOfWeekWeights: 7-element array [Sun..Sat], max=1.0, null to disable
10
17
  * - hourOfDayWeights: 24-element array [0h..23h UTC], max=1.0, null to disable
11
- *
12
- * Some presets also suggest bornRecentBias and percentUsersBornInDataset,
13
- * but those are top-level dungeon config — presets only set them if not already specified.
14
18
  */
15
19
 
16
20
  // Real-world Mixpanel DOW pattern: weekday-heavy, Saturday valley
@@ -27,11 +31,11 @@ export const REAL_HOD = [
27
31
  export const FLAT_DOW = [1, 1, 1, 1, 1, 1, 1];
28
32
  export const FLAT_HOD = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
29
33
 
30
- /** @type {Record<string, {peaks: (numDays: number) => number, deviation: number, mean: number, dayOfWeekWeights: number[]|null, hourOfDayWeights: number[]|null, bornRecentBias?: number, percentUsersBornInDataset?: number}>} */
34
+ /** @type {Record<string, {peaks: (numDays: number) => number, deviation: number, mean: number, dayOfWeekWeights: number[]|null, hourOfDayWeights: number[]|null}>} */
31
35
  export const SOUP_PRESETS = {
32
36
  /**
33
37
  * steady — Mature SaaS / Stable Product
34
- * Nearly flat day-over-day, slight weekly pattern, minimal growth trend.
38
+ * Tighter clusters, real-world DOW/HOD weights.
35
39
  */
36
40
  steady: {
37
41
  peaks: (numDays) => Math.max(5, numDays * 2),
@@ -39,13 +43,11 @@ export const SOUP_PRESETS = {
39
43
  mean: 0,
40
44
  dayOfWeekWeights: REAL_DOW,
41
45
  hourOfDayWeights: REAL_HOD,
42
- bornRecentBias: 0.1,
43
- percentUsersBornInDataset: 10,
44
46
  },
45
47
 
46
48
  /**
47
49
  * growth — Growing Startup (DEFAULT)
48
- * Gradual uptrend with visible weekly peaks. This is the default behavior.
50
+ * Standard clustering with real-world weekly + daily rhythm.
49
51
  */
50
52
  growth: {
51
53
  peaks: (numDays) => Math.max(5, numDays * 2),
@@ -53,13 +55,11 @@ export const SOUP_PRESETS = {
53
55
  mean: 0,
54
56
  dayOfWeekWeights: REAL_DOW,
55
57
  hourOfDayWeights: REAL_HOD,
56
- bornRecentBias: 0.3,
57
- percentUsersBornInDataset: 15,
58
58
  },
59
59
 
60
60
  /**
61
61
  * spiky — Event-Driven / Bursty
62
- * Clear peaks and valleys, dramatic variation. Fewer Gaussian clusters + tight deviation.
62
+ * Fewer Gaussian clusters + tight deviation = visible peaks and valleys.
63
63
  */
64
64
  spiky: {
65
65
  peaks: (numDays) => Math.max(4, Math.ceil(numDays / 15)),
@@ -67,13 +67,11 @@ export const SOUP_PRESETS = {
67
67
  mean: 0,
68
68
  dayOfWeekWeights: REAL_DOW,
69
69
  hourOfDayWeights: REAL_HOD,
70
- bornRecentBias: 0.3,
71
- percentUsersBornInDataset: 20,
72
70
  },
73
71
 
74
72
  /**
75
73
  * seasonal — Strong Cyclical Patterns
76
- * 3-4 major waves across the dataset. Very few peaks create dramatic macro trends.
74
+ * 4 major waves across the dataset for dramatic macro trends.
77
75
  */
78
76
  seasonal: {
79
77
  peaks: () => 4,
@@ -81,13 +79,11 @@ export const SOUP_PRESETS = {
81
79
  mean: 0,
82
80
  dayOfWeekWeights: REAL_DOW,
83
81
  hourOfDayWeights: REAL_HOD,
84
- bornRecentBias: 0.2,
85
- percentUsersBornInDataset: 25,
86
82
  },
87
83
 
88
84
  /**
89
85
  * global — Distributed Users Across Timezones
90
- * Very flat hourly + daily distribution. No cyclical patterns.
86
+ * Flat hourly + daily distribution. No cyclical patterns.
91
87
  */
92
88
  global: {
93
89
  peaks: (numDays) => Math.max(5, numDays * 2),
@@ -95,15 +91,12 @@ export const SOUP_PRESETS = {
95
91
  mean: 0,
96
92
  dayOfWeekWeights: FLAT_DOW,
97
93
  hourOfDayWeights: FLAT_HOD,
98
- bornRecentBias: 0,
99
- percentUsersBornInDataset: 10,
100
94
  },
101
95
 
102
96
  /**
103
97
  * churny — High Churn / Declining Product
104
- * Flat distribution (no growth trend). All users pre-exist the dataset,
105
- * so there's no acceleration. Combine with an "everything" hook that
106
- * filters late events to create a true declining shape.
98
+ * Standard intra-week rhythm. Combine with macro: "decline" and an
99
+ * "everything" hook that filters late events to create a true declining shape.
107
100
  */
108
101
  churny: {
109
102
  peaks: (numDays) => Math.max(5, numDays * 2),
@@ -111,8 +104,6 @@ export const SOUP_PRESETS = {
111
104
  mean: 0,
112
105
  dayOfWeekWeights: REAL_DOW,
113
106
  hourOfDayWeights: REAL_HOD,
114
- bornRecentBias: 0,
115
- percentUsersBornInDataset: 5,
116
107
  },
117
108
 
118
109
  /**
@@ -125,8 +116,6 @@ export const SOUP_PRESETS = {
125
116
  mean: 0,
126
117
  dayOfWeekWeights: REAL_DOW,
127
118
  hourOfDayWeights: REAL_HOD,
128
- bornRecentBias: 0.5,
129
- percentUsersBornInDataset: 40,
130
119
  },
131
120
  };
132
121
 
@@ -137,7 +126,7 @@ export const PRESET_NAMES = Object.keys(SOUP_PRESETS);
137
126
  * Resolves a soup config — handles string presets, preset+overrides, and raw objects.
138
127
  * @param {string | object} soup - Soup config from dungeon
139
128
  * @param {number} numDays - Number of days in the dataset
140
- * @returns {{ soup: object, suggestedBornRecentBias?: number, suggestedPercentUsersBornInDataset?: number }}
129
+ * @returns {{ soup: object }}
141
130
  */
142
131
  export function resolveSoup(soup, numDays) {
143
132
  if (!soup) return { soup: {} };
@@ -156,8 +145,6 @@ export function resolveSoup(soup, numDays) {
156
145
  dayOfWeekWeights: preset.dayOfWeekWeights,
157
146
  hourOfDayWeights: preset.hourOfDayWeights,
158
147
  },
159
- suggestedBornRecentBias: preset.bornRecentBias,
160
- suggestedPercentUsersBornInDataset: preset.percentUsersBornInDataset,
161
148
  };
162
149
  }
163
150
 
@@ -176,11 +163,7 @@ export function resolveSoup(soup, numDays) {
176
163
  };
177
164
  // Apply overrides (excluding the 'preset' key itself)
178
165
  const { preset: _, ...overrides } = soup;
179
- return {
180
- soup: { ...base, ...overrides },
181
- suggestedBornRecentBias: preset.bornRecentBias,
182
- suggestedPercentUsersBornInDataset: preset.percentUsersBornInDataset,
183
- };
166
+ return { soup: { ...base, ...overrides } };
184
167
  }
185
168
 
186
169
  // Raw object: { peaks: 10, deviation: 2 } — pass through unchanged
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ak--47/dungeon-master",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "generate fancy datasets",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -19,10 +19,16 @@
19
19
  "lib/",
20
20
  "dungeons/",
21
21
  "!dungeons/customers/",
22
+ "!dungeons/user/",
22
23
  "scripts/",
24
+ "!scripts/experiments/",
23
25
  "package.json",
24
- "README.md"
26
+ "README.md",
27
+ "CHANGELOG.md"
25
28
  ],
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
26
32
  "publishConfig": {
27
33
  "access": "public"
28
34
  },
package/types.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  /**
2
- * most of the time, the value of a property is a primitive
2
+ * Primitive scalar types that property values resolve to. Property values may
3
+ * also be plain object literals (nested records) — `Record<string, any>` is the
4
+ * narrowest TS can express here without making `Primitives` self-recursive
5
+ * (which it can't be, because `ValueValid` is built on top of it).
3
6
  */
4
7
  type Primitives = string | number | boolean | Date | Record<string, any>;
5
8
 
6
9
  /**
7
- * a "validValue" can be a primitive, an array of primitives, or a function that returns a primitive
10
+ * A "validValue" can be a primitive, an array of valid values, or a thunk that
11
+ * returns one. Configs use this everywhere properties are user-defined.
8
12
  */
9
13
  export type ValueValid = Primitives | ValueValid[] | (() => ValueValid);
10
14
 
@@ -23,10 +27,12 @@ export interface Dungeon {
23
27
  epochStart?: number;
24
28
  /** Explicit end of dataset window (unix seconds). Defaults to FIXED_NOW. */
25
29
  epochEnd?: number;
26
- /** Target total number of events to generate across all users. */
30
+ /** Target total number of events to generate across all users. Fallback when avgEventsPerUserPerDay is not set; otherwise derived from rate × numUsers × numDays. */
27
31
  numEvents?: number;
28
32
  /** Number of unique users to generate. */
29
33
  numUsers?: number;
34
+ /** Average events per user per active day. The canonical event-volume primitive — born-late users get this rate × their remaining window, so per-day density stays constant. If both this and numEvents are set, this wins. */
35
+ avgEventsPerUserPerDay?: number;
30
36
  /** Output format for files written to disk. */
31
37
  format?: "csv" | "json" | "parquet" | string;
32
38
  /** Mixpanel data residency region. */
@@ -106,9 +112,11 @@ export interface Dungeon {
106
112
  groupEvents?: GroupEventConfig[];
107
113
  /** Lookup table definitions for dimension tables. */
108
114
  lookupTables?: LookupTableSchema[];
109
- /** TimeSoup configuration: controls the temporal distribution of events (peaks, deviation, mean). */
115
+ /** TimeSoup configuration: shapes intra-week and intra-day rhythm (peaks, deviation, DOW/HOD weights). Pair with `macro` for big-picture trend control. */
110
116
  soup?: soup;
111
- /** Hook function called on every data point. The primary mechanism for engineering deliberate trends and patterns. */
117
+ /** Macro trend shape across the full dataset window: birth distribution + per-user event allocation. Default: "flat". Use "growth"/"viral"/"steady"/"decline" or a custom object. */
118
+ macro?: macro;
119
+ /** Hook function called on every data point. The primary mechanism for engineering deliberate trends and patterns. The `any` is intentional — `record` and `meta` shapes vary by hook type; narrow inside the function (see `HookMeta*` types). */
112
120
  hook?: Hook<any>;
113
121
 
114
122
  // ── Advanced Features ──
@@ -135,10 +143,14 @@ export interface Dungeon {
135
143
  [key: string]: any;
136
144
 
137
145
  // ── Distribution Controls ──
138
- /** Percentage of users whose account creation falls within the dataset window (vs. pre-existing). Default: 15 */
146
+ // These three knobs are normally set by the `macro` preset (default "flat").
147
+ // Setting them on the dungeon config directly overrides the preset's value.
148
+ /** Percentage of users whose account creation falls within the dataset window (vs. pre-existing). Default (from macro: "flat"): 15 */
139
149
  percentUsersBornInDataset?: number;
140
- /** Bias toward recent birth dates for users born in dataset (0 = uniform, 1 = heavily recent). Default: 0.3 */
150
+ /** Bias for birth dates of users born in dataset. -1..1; negative = early skew, positive = recent skew, 0 = uniform. Default (from macro: "flat"): 0 */
141
151
  bornRecentBias?: number;
152
+ /** How pre-existing users' first event time is placed. "pinned" stacks them all at FIXED_BEGIN; "uniform" spreads across [FIXED_BEGIN-30d, FIXED_BEGIN]. Default (from macro: "flat"): "uniform" */
153
+ preExistingSpread?: "pinned" | "uniform";
142
154
  }
143
155
 
144
156
  export type SCDProp = {
@@ -183,6 +195,43 @@ export type SoupConfig = {
183
195
  */
184
196
  type soup = SoupPreset | SoupConfig;
185
197
 
198
+ /**
199
+ * Macro preset names for big-picture trend shape across the dataset window.
200
+ * Macro is orthogonal to soup: macro shapes the whole-window trend (births,
201
+ * growth, decline); soup shapes the intra-week and intra-day rhythm.
202
+ */
203
+ export type MacroPreset = "flat" | "steady" | "growth" | "viral" | "decline";
204
+
205
+ /**
206
+ * Macro configuration object — fine-grained big-picture trend control.
207
+ */
208
+ export type MacroConfig = {
209
+ /** Use a named macro preset as the base, then override individual fields. */
210
+ preset?: MacroPreset;
211
+ /** Bias for birth dates. -1..1; negative = early skew, positive = recent skew, 0 = uniform. */
212
+ bornRecentBias?: number;
213
+ /** Percentage of users born in dataset window (0..100). */
214
+ percentUsersBornInDataset?: number;
215
+ /** "pinned" = pre-existing users stack at FIXED_BEGIN; "uniform" = spread across [FIXED_BEGIN-30d, FIXED_BEGIN]. */
216
+ preExistingSpread?: "pinned" | "uniform";
217
+ };
218
+
219
+ /** Big-picture trend shape: preset string, config object, or preset+overrides. */
220
+ type macro = MacroPreset | MacroConfig;
221
+
222
+ /** Public alias for the `soup` config union (preset string or config object). */
223
+ export type Soup = soup;
224
+
225
+ /** Public alias for the `macro` config union (preset string or config object). */
226
+ export type Macro = macro;
227
+
228
+ /** Resolved macro values after preset + override resolution. Used internally. */
229
+ export interface ResolvedMacro {
230
+ bornRecentBias: number;
231
+ percentUsersBornInDataset: number;
232
+ preExistingSpread: "pinned" | "uniform";
233
+ }
234
+
186
235
  /**
187
236
  * Hook types and when they fire (in order per user):
188
237
  * - "user" — user profile object (mutate in-place, return ignored)
@@ -213,31 +262,129 @@ export type hookTypes =
213
262
 
214
263
  /**
215
264
  * A hook function that receives every piece of data as it flows through the pipeline.
216
- * @param record - The data being processed (event, profile, array of events, etc.)
217
- * @param type - Which hook type is firing
218
- * @param meta - Contextual metadata (varies by type; "everything" includes meta.profile and meta.scd)
265
+ *
266
+ * The runtime signature is intentionally permissive (`any`) because `record` and `meta`
267
+ * vary by `type`. Use the `HookMeta*` interfaces below as convenience types when narrowing
268
+ * inside your hook (e.g. `if (type === "event") { const m = meta as HookMetaEvent; ... }`).
269
+ *
270
+ * Return-value semantics:
271
+ * - "event": return value REPLACES the event (must be the event object).
272
+ * - "everything": return an array to REPLACE the user's event list (filter/inject/dedupe).
273
+ * - "user", "scd-pre", "funnel-pre", "funnel-post": return value is IGNORED — mutate in place.
274
+ * - storage-only ("ad-spend", "group", "mirror", "lookup"): return value is IGNORED.
275
+ *
276
+ * @param record - The data being processed (event, profile, array of events, funnel config, etc.).
277
+ * @param type - Which hook type is firing — see `hookTypes`.
278
+ * @param meta - Contextual metadata. Shape depends on `type` — see `HookMeta*` interfaces.
219
279
  */
220
280
  export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
221
281
 
282
+ /** Meta passed to the "event" hook. */
283
+ export interface HookMetaEvent {
284
+ /** The user this event belongs to (only `distinct_id` is guaranteed). */
285
+ user: { distinct_id: string };
286
+ /** The fully-resolved dungeon config. */
287
+ config: Dungeon;
288
+ }
289
+
290
+ /** Meta passed to the "user" hook (fires when a user profile is created). */
291
+ export interface HookMetaUser {
292
+ /** The user object being constructed (mutate in place). */
293
+ user: UserProfile;
294
+ /** The fully-resolved dungeon config. */
295
+ config: Dungeon;
296
+ /** True if the user's account creation falls inside the dataset window. */
297
+ userIsBornInDataset: boolean;
298
+ }
299
+
300
+ /** Meta passed to the "scd-pre" hook (fires per SCD prop, before insertion). */
301
+ export interface HookMetaScdPre {
302
+ /** The user profile that owns these SCD entries. */
303
+ profile: UserProfile;
304
+ /** The SCD prop key being generated (e.g. "plan", "tier"). */
305
+ type: string;
306
+ /** The full SCD entry list for this prop (mutate in place). */
307
+ scd: SCDSchema[];
308
+ /** The fully-resolved dungeon config. */
309
+ config: Dungeon;
310
+ /** All SCD prop arrays generated so far for this user, keyed by prop name. */
311
+ allSCDs: Record<string, SCDSchema[]>;
312
+ }
313
+
314
+ /** Meta passed to the "funnel-pre" hook (mutate funnel before generating events). */
315
+ export interface HookMetaFunnelPre {
316
+ user: { distinct_id: string };
317
+ profile: UserProfile;
318
+ scd: Record<string, SCDSchema[]>;
319
+ funnel: Funnel;
320
+ config: Dungeon;
321
+ /** Unix seconds — earliest possible event time for this funnel's first step. */
322
+ firstEventTime: number;
323
+ }
324
+
325
+ /** Meta passed to the "funnel-post" hook (mutate generated funnel events in place). */
326
+ export interface HookMetaFunnelPost {
327
+ user: { distinct_id: string };
328
+ profile: UserProfile;
329
+ scd: Record<string, SCDSchema[]>;
330
+ funnel: Funnel;
331
+ config: Dungeon;
332
+ }
333
+
334
+ /** Meta passed to the "everything" hook — most powerful hook (sees all events for one user). */
335
+ export interface HookMetaEverything {
336
+ /** The user's profile, including merged persona/region/attribution properties. */
337
+ profile: UserProfile;
338
+ /** All SCD entries for this user, keyed by prop name. */
339
+ scd: Record<string, SCDSchema[]>;
340
+ /** The fully-resolved dungeon config. */
341
+ config: Dungeon;
342
+ /** True if the user's account creation falls inside the dataset window. */
343
+ userIsBornInDataset: boolean;
344
+ }
345
+
222
346
  export interface hookArrayOptions<T> {
347
+ /** Transform/validate function applied to every record on push. */
223
348
  hook?: Hook<T>;
349
+ /** What this array stores — controls hook-firing semantics in the storage layer. */
224
350
  type?: hookTypes;
351
+ /** Output filename (no extension; format adds it). Used by storage's batch writer. */
225
352
  filename?: string;
353
+ /** Output filepath used when writing batches to disk. */
354
+ filepath?: string;
355
+ /** Output serialization format. */
226
356
  format?: "csv" | "json" | "parquet" | string;
357
+ /** Max parallel disk writes. */
227
358
  concurrency?: number;
359
+ /** Generation context (config, runtime, defaults). */
228
360
  context?: Context;
229
- [key: string]: any;
230
361
  }
231
362
 
232
363
  /**
233
- * an enriched array is an array that has a hookPush method that can be used to transform-then-push items into the array
364
+ * an enriched array is an array that has a hookPush method that can be used to transform-then-push items into the array.
365
+ *
366
+ * Storage callers also tag the array with a key identifying what it stores
367
+ * (e.g. SCD prop name, group key, lookup table key). The fields are optional
368
+ * because not every HookedArray needs them; mixpanel-sender / user-loop read
369
+ * them when present to route uploads correctly.
234
370
  */
235
371
  export interface HookedArray<T> extends Array<T> {
236
- hookPush: (item: T | T[], ...meta: any[]) => Promise<any>;
372
+ /** Transform-then-push. Resolves once the item (and any auto-flushed batch) is persisted. */
373
+ hookPush: (item: T | T[], ...meta: unknown[]) => Promise<void>;
374
+ /** Force-flush any pending batch to disk. */
237
375
  flush: () => Promise<void>;
376
+ /** Absolute path of the directory batches will be written to. */
238
377
  getWriteDir: () => string;
378
+ /** Absolute path (with extension) of the next batch file. */
239
379
  getWritePath: () => string;
240
- [key: string]: any;
380
+ /** SCD prop name this array carries (only set on SCD HookedArrays). */
381
+ scdKey?: string;
382
+ /** Entity type for SCDs ("user" or a group key). */
383
+ entityType?: string;
384
+ /** Group key this array carries (only set on group profile HookedArrays). */
385
+ groupKey?: string;
386
+ /** Lookup table key this array carries (only set on lookup table HookedArrays). */
387
+ lookupKey?: string;
241
388
  }
242
389
 
243
390
  export type AllData =
@@ -245,8 +392,7 @@ export type AllData =
245
392
  | HookedArray<UserProfile>
246
393
  | HookedArray<GroupProfileSchema>
247
394
  | HookedArray<LookupTableSchema>
248
- | HookedArray<SCDSchema>
249
- | any[];
395
+ | HookedArray<SCDSchema>;
250
396
 
251
397
  /**
252
398
  * the storage object is a key-value store that holds arrays of data
@@ -277,16 +423,33 @@ export interface RuntimeState {
277
423
  /**
278
424
  * Default data factories for generating realistic test data
279
425
  */
426
+ /**
427
+ * Default data factories — pre-resolved at context creation time so user-loop
428
+ * doesn't re-evaluate weighted picker arrays on every iteration.
429
+ */
280
430
  export interface Defaults {
281
- locationsUsers: () => any[];
282
- locationsEvents: () => any[];
283
- iOSDevices: () => any[];
284
- androidDevices: () => any[];
285
- desktopDevices: () => any[];
286
- browsers: () => any[];
287
- campaigns: () => any[];
288
- devicePools: { android: any[]; ios: any[]; desktop: any[] };
289
- allDevices: any[];
431
+ /** Location pools applied to user profiles (city, region, country, lat/lng). */
432
+ locationsUsers: () => Record<string, ValueValid>[];
433
+ /** Location pools applied to events. */
434
+ locationsEvents: () => Record<string, ValueValid>[];
435
+ /** iOS device pool (model, os version, etc.). */
436
+ iOSDevices: () => Record<string, ValueValid>[];
437
+ /** Android device pool. */
438
+ androidDevices: () => Record<string, ValueValid>[];
439
+ /** Desktop device pool (browser, screen resolution, etc.). */
440
+ desktopDevices: () => Record<string, ValueValid>[];
441
+ /** Browser/UA pool. */
442
+ browsers: () => Record<string, ValueValid>[];
443
+ /** UTM campaign pool used when `hasCampaigns: true`. */
444
+ campaigns: () => Record<string, ValueValid>[];
445
+ /** Pre-built per-platform device arrays selected once at context creation. */
446
+ devicePools: {
447
+ android: Record<string, ValueValid>[];
448
+ ios: Record<string, ValueValid>[];
449
+ desktop: Record<string, ValueValid>[];
450
+ };
451
+ /** Flat union of every device in `devicePools` — used when no platform filter applies. */
452
+ allDevices: Record<string, ValueValid>[];
290
453
  }
291
454
 
292
455
  /**
@@ -297,7 +460,8 @@ export interface Context {
297
460
  config: Dungeon;
298
461
  storage: Storage | null;
299
462
  defaults: Defaults;
300
- campaigns: any[];
463
+ /** Pre-built UTM campaign pool (used when `hasCampaigns: true`). */
464
+ campaigns: Record<string, ValueValid>[];
301
465
  runtime: RuntimeState;
302
466
  FIXED_NOW: number;
303
467
  FIXED_BEGIN?: number;
@@ -494,7 +658,8 @@ export interface LookupTableSchema {
494
658
 
495
659
  export interface LookupTableData {
496
660
  key: string;
497
- data: any[];
661
+ /** Generated rows for this lookup table. Each row is a flat record keyed by attribute name. */
662
+ data: Record<string, ValueValid>[];
498
663
  }
499
664
 
500
665
  export interface SCDSchema {
@@ -506,7 +671,8 @@ export interface SCDSchema {
506
671
 
507
672
  export interface GroupProfileSchema {
508
673
  key: string;
509
- data: any[];
674
+ /** Generated group profile rows. Each row is a flat record keyed by group property name. */
675
+ data: Record<string, ValueValid>[];
510
676
  }
511
677
 
512
678
  /**
@@ -523,15 +689,25 @@ type ImportResult = import("mixpanel-import").ImportResults;
523
689
  * the end result of the data generation
524
690
  */
525
691
  export type Result = {
692
+ /** Generated events. */
526
693
  eventData: EventSchema[];
694
+ /** Mirror datasets (transformed copies of `eventData`). */
527
695
  mirrorEventData: EventSchema[];
528
- userProfilesData: any[];
529
- scdTableData: any[][];
696
+ /** User profiles. */
697
+ userProfilesData: UserProfile[];
698
+ /** SCD entries — one inner array per SCD prop. */
699
+ scdTableData: SCDSchema[][];
700
+ /** Ad-spend events (only populated when `hasAdSpend: true`). */
530
701
  adSpendData: EventSchema[];
702
+ /** Group profiles — one inner array per group key. */
531
703
  groupProfilesData: GroupProfileSchema[][];
704
+ /** Lookup tables — one inner array per table. */
532
705
  lookupTableData: LookupTableData[][];
706
+ /** Mixpanel import results (only populated when a token was provided). */
533
707
  importResults?: ImportResults;
708
+ /** Absolute paths of all files written to disk. */
534
709
  files?: string[];
710
+ /** Timing information. */
535
711
  time?: {
536
712
  start: number;
537
713
  end: number;
@@ -589,7 +765,7 @@ export interface WorldEvent {
589
765
  /** Conversion rate modifier during this event. */
590
766
  conversionModifier?: number;
591
767
  /** Properties injected into affected events. */
592
- injectProps?: Record<string, any>;
768
+ injectProps?: Record<string, ValueValid>;
593
769
  /** Which events are affected ("*" for all, or array of event names). */
594
770
  affectsEvents?: string[] | "*";
595
771
  /** Aftermath period after the event ends. */
@@ -752,7 +928,7 @@ export interface GeoRegion {
752
928
  /** UTC timezone offset for this region (e.g., -5 for EST). */
753
929
  timezoneOffset: number;
754
930
  /** Properties injected for users in this region. */
755
- properties?: Record<string, any>;
931
+ properties?: Record<string, ValueValid>;
756
932
  }
757
933
 
758
934
  /**
@@ -792,9 +968,9 @@ export interface FeatureConfig {
792
968
  /** Property name to inject on events. */
793
969
  property: string;
794
970
  /** Possible values for the property. First value is the "before" default if defaultBefore not set. */
795
- values: any[];
971
+ values: ValueValid[];
796
972
  /** Default value before the feature launches. If not set, property doesn't exist before launch. */
797
- defaultBefore?: any;
973
+ defaultBefore?: ValueValid;
798
974
  /** Which events are affected ("*" for all, or array of event names). */
799
975
  affectsEvents?: string[] | "*";
800
976
  /** Conversion rate lift for users who adopted the feature. */
@@ -802,7 +978,7 @@ export interface FeatureConfig {
802
978
  /** Resolved logistic curve params (set by config-validator). */
803
979
  _resolvedCurve?: { k: number; midpoint: number };
804
980
  /** Pre-computed adopted values (set by config-validator). */
805
- _adoptedValues?: any[];
981
+ _adoptedValues?: ValueValid[];
806
982
  }
807
983
 
808
984
  /**
@@ -830,7 +1006,7 @@ export interface AnomalyConfig {
830
1006
  /** For burst/coordinated: number of events to inject. */
831
1007
  count?: number;
832
1008
  /** Properties injected on anomalous events. */
833
- properties?: Record<string, any>;
1009
+ properties?: Record<string, ValueValid>;
834
1010
  /** Resolved absolute start time in unix seconds (set by config-validator). */
835
1011
  _startUnix?: number;
836
1012
  /** Resolved absolute end time in unix seconds (set by config-validator). */
@@ -869,8 +1045,8 @@ export declare function loadFromFile(filePath: string): Promise<Dungeon>;
869
1045
  export declare function loadFromText(code: string): Promise<Dungeon>;
870
1046
  /** Parse a JSON dungeon (UI schema format) into a runnable config */
871
1047
  export declare function parseJSONDungeon(json: object): Dungeon;
872
- /** Validate that an object has the minimum shape of a dungeon config */
873
- export declare function validateDungeonShape(config: any): void;
1048
+ /** Validate that an object has the minimum shape of a dungeon config. Throws on shape violations. */
1049
+ export declare function validateDungeonShape(config: unknown): void;
874
1050
 
875
1051
  // ============= Text Generator Types =============
876
1052
 
@@ -1049,7 +1225,7 @@ export interface TextMetadata {
1049
1225
  /** Keywords that were injected */
1050
1226
  injectedKeywords?: string[];
1051
1227
  /** User persona information */
1052
- persona?: Record<string, any>;
1228
+ persona?: Record<string, ValueValid>;
1053
1229
  /** Flesch reading ease score */
1054
1230
  readabilityScore?: number;
1055
1231
  /** Text style used */
@@ -1170,13 +1346,14 @@ export interface TimeSoupOptions {
1170
1346
  }
1171
1347
 
1172
1348
  /**
1173
- * Test context configuration for unit/integration tests
1349
+ * Test context configuration for unit/integration tests. Looser than `Context`
1350
+ * by design — tests routinely attach ad-hoc fixtures, so the index signature stays.
1174
1351
  */
1175
1352
  export interface TestContext {
1176
1353
  config: Dungeon;
1177
1354
  storage: Storage | null;
1178
1355
  defaults: Defaults;
1179
- campaigns: any[];
1356
+ campaigns: Record<string, ValueValid>[];
1180
1357
  runtime: RuntimeState;
1181
- [key: string]: any;
1358
+ [key: string]: unknown;
1182
1359
  }
File without changes