@ak--47/dungeon-master 1.2.3 → 1.3.1
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/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -4
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +5 -6
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +3 -3
- package/dungeons/vertical/ai-platform.js +858 -0
- package/dungeons/vertical/community.js +84 -40
- package/dungeons/vertical/crypto.js +830 -0
- package/dungeons/vertical/dating.js +744 -0
- package/dungeons/vertical/devtools.js +175 -69
- package/dungeons/vertical/ecommerce.js +242 -94
- package/dungeons/vertical/education.js +330 -313
- package/dungeons/vertical/fintech.js +442 -313
- package/dungeons/vertical/fitness.js +143 -61
- package/dungeons/vertical/food-delivery.js +327 -353
- package/dungeons/vertical/gaming.js +912 -382
- package/dungeons/vertical/healthcare.js +142 -63
- package/dungeons/vertical/insurance-application.js +170 -76
- package/dungeons/vertical/logistics.js +115 -20
- package/dungeons/vertical/marketplace.js +152 -58
- package/dungeons/vertical/media.js +248 -384
- package/dungeons/vertical/real-estate.js +781 -0
- package/dungeons/vertical/sass.js +255 -266
- package/dungeons/vertical/social.js +264 -206
- package/dungeons/vertical/travel.js +117 -41
- package/index.js +17 -17
- package/lib/core/config-validator.js +159 -31
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +81 -47
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/lib/utils/utils.js +71 -39
- package/package.json +8 -2
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +251 -51
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -438
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -1,42 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Verify Runner — runs a dungeon
|
|
2
|
+
* Verify Runner — runs a dungeon for hook verification.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Default mode: FULL FIDELITY — runs the dungeon with its own configured
|
|
5
|
+
* numUsers / avgEventsPerUserPerDay / numDays. This is the only way to
|
|
6
|
+
* verify the trends a benchmark consumer will actually see.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
8
|
+
* --small mode: ~1K users with avgEventsPerUserPerDay scaled proportionally
|
|
9
|
+
* so total events stay near 100K. Use only for fast smoke checks; do NOT
|
|
10
|
+
* ship verification verdicts based on --small runs.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node scripts/verify-runner.mjs <dungeon-path> [run-name] [--small]
|
|
14
|
+
*
|
|
15
|
+
* Examples:
|
|
16
|
+
* node scripts/verify-runner.mjs dungeons/vertical/gaming.js verify-gaming
|
|
17
|
+
* node scripts/verify-runner.mjs dungeons/vertical/gaming.js verify-gaming --small
|
|
7
18
|
*/
|
|
8
19
|
import generate from '../index.js';
|
|
9
20
|
import path from 'path';
|
|
10
21
|
|
|
11
|
-
const
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const flags = new Set(args.filter(a => a.startsWith('--')));
|
|
24
|
+
const positional = args.filter(a => !a.startsWith('--'));
|
|
25
|
+
|
|
26
|
+
const dungeonPath = positional[0];
|
|
12
27
|
if (!dungeonPath) {
|
|
13
|
-
console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]');
|
|
28
|
+
console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name] [--small]');
|
|
14
29
|
process.exit(1);
|
|
15
30
|
}
|
|
16
31
|
|
|
17
|
-
const runName =
|
|
32
|
+
const runName = positional[1] || 'verify-hooks';
|
|
33
|
+
const isSmall = flags.has('--small');
|
|
18
34
|
const absolutePath = path.isAbsolute(dungeonPath)
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
? dungeonPath
|
|
36
|
+
: path.resolve(process.cwd(), dungeonPath);
|
|
21
37
|
|
|
22
38
|
const { default: config } = await import(absolutePath);
|
|
23
39
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
let override;
|
|
41
|
+
|
|
42
|
+
if (isSmall) {
|
|
43
|
+
// SMALL mode: 1K users, scaled per-day rate so total events ≈ 100K.
|
|
44
|
+
// Preserves the dungeon's per-user-per-day shape while shrinking the dataset.
|
|
45
|
+
// Use only for quick smoke checks — verdicts must come from full fidelity.
|
|
46
|
+
const numUsers = 1000;
|
|
47
|
+
const numDays = config.numDays || 100;
|
|
48
|
+
const targetTotal = 100_000;
|
|
49
|
+
const scaledRate = targetTotal / (numUsers * numDays);
|
|
50
|
+
override = {
|
|
51
|
+
...config,
|
|
52
|
+
token: '',
|
|
53
|
+
numUsers,
|
|
54
|
+
numDays,
|
|
55
|
+
avgEventsPerUserPerDay: scaledRate,
|
|
56
|
+
numEvents: undefined,
|
|
57
|
+
format: 'json',
|
|
58
|
+
gzip: false,
|
|
59
|
+
writeToDisk: true,
|
|
60
|
+
name: runName,
|
|
61
|
+
concurrency: 1,
|
|
62
|
+
verbose: false,
|
|
63
|
+
};
|
|
64
|
+
} else {
|
|
65
|
+
// FULL FIDELITY: use the dungeon's own scale settings as-shipped.
|
|
66
|
+
// Full-scale runs can take minutes for 50K-user dungeons; that's expected.
|
|
67
|
+
override = {
|
|
68
|
+
...config,
|
|
69
|
+
token: '',
|
|
70
|
+
format: 'json',
|
|
71
|
+
gzip: false,
|
|
72
|
+
writeToDisk: true,
|
|
73
|
+
name: runName,
|
|
74
|
+
concurrency: config.concurrency || 1,
|
|
75
|
+
verbose: false,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const t0 = Date.now();
|
|
80
|
+
const results = await generate(override);
|
|
81
|
+
const wallMs = Date.now() - t0;
|
|
36
82
|
|
|
37
83
|
console.log(JSON.stringify({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
mode: isSmall ? 'small' : 'full',
|
|
85
|
+
eventCount: results.eventCount,
|
|
86
|
+
userCount: results.userCount,
|
|
87
|
+
files: results.files,
|
|
88
|
+
duration: results.time?.human || `${wallMs}ms`,
|
|
89
|
+
wallMs,
|
|
42
90
|
}));
|
package/types.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
*
|
|
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
|
|
|
@@ -17,16 +21,29 @@ export interface Dungeon {
|
|
|
17
21
|
token?: string;
|
|
18
22
|
/** RNG seed for reproducible output. Same seed + concurrency=1 = identical data. */
|
|
19
23
|
seed?: string;
|
|
20
|
-
/** Number of days the dataset spans (
|
|
24
|
+
/** Number of days the dataset spans. Used as fallback when datasetStart/datasetEnd are NOT both set — window becomes (today_start - numDays, today_start). Default: 30. When datasetStart/datasetEnd ARE both set, numDays is recomputed from the window and any user-supplied value is ignored (with a warning). */
|
|
21
25
|
numDays?: number;
|
|
22
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Explicit start of the dataset window. Pin BOTH `datasetStart` and `datasetEnd` for
|
|
28
|
+
* bit-exact deterministic runs. Accepts ISO string ("2026-01-01T00:00:00Z"), unix
|
|
29
|
+
* seconds (1735689600), unix milliseconds (1735689600000), or anything `dayjs()`
|
|
30
|
+
* can parse. Setting only one of datasetStart/datasetEnd throws.
|
|
31
|
+
*/
|
|
32
|
+
datasetStart?: string | number;
|
|
33
|
+
/**
|
|
34
|
+
* Explicit end of the dataset window. See `datasetStart` — both must be set together.
|
|
35
|
+
*/
|
|
36
|
+
datasetEnd?: string | number;
|
|
37
|
+
/** @deprecated Legacy alias internally aliased to datasetStart on validated config. Prefer `datasetStart`. */
|
|
23
38
|
epochStart?: number;
|
|
24
|
-
/**
|
|
39
|
+
/** @deprecated Legacy alias internally aliased to datasetEnd on validated config. Prefer `datasetEnd`. */
|
|
25
40
|
epochEnd?: number;
|
|
26
|
-
/** Target total number of events to generate across all users. */
|
|
41
|
+
/** Target total number of events to generate across all users. Fallback when avgEventsPerUserPerDay is not set; otherwise derived from rate × numUsers × numDays. */
|
|
27
42
|
numEvents?: number;
|
|
28
43
|
/** Number of unique users to generate. */
|
|
29
44
|
numUsers?: number;
|
|
45
|
+
/** 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. */
|
|
46
|
+
avgEventsPerUserPerDay?: number;
|
|
30
47
|
/** Output format for files written to disk. */
|
|
31
48
|
format?: "csv" | "json" | "parquet" | string;
|
|
32
49
|
/** Mixpanel data residency region. */
|
|
@@ -106,9 +123,11 @@ export interface Dungeon {
|
|
|
106
123
|
groupEvents?: GroupEventConfig[];
|
|
107
124
|
/** Lookup table definitions for dimension tables. */
|
|
108
125
|
lookupTables?: LookupTableSchema[];
|
|
109
|
-
/** TimeSoup configuration:
|
|
126
|
+
/** TimeSoup configuration: shapes intra-week and intra-day rhythm (peaks, deviation, DOW/HOD weights). Pair with `macro` for big-picture trend control. */
|
|
110
127
|
soup?: soup;
|
|
111
|
-
/**
|
|
128
|
+
/** 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. */
|
|
129
|
+
macro?: macro;
|
|
130
|
+
/** 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
131
|
hook?: Hook<any>;
|
|
113
132
|
|
|
114
133
|
// ── Advanced Features ──
|
|
@@ -135,10 +154,14 @@ export interface Dungeon {
|
|
|
135
154
|
[key: string]: any;
|
|
136
155
|
|
|
137
156
|
// ── Distribution Controls ──
|
|
138
|
-
|
|
157
|
+
// These three knobs are normally set by the `macro` preset (default "flat").
|
|
158
|
+
// Setting them on the dungeon config directly overrides the preset's value.
|
|
159
|
+
/** Percentage of users whose account creation falls within the dataset window (vs. pre-existing). Default (from macro: "flat"): 15 */
|
|
139
160
|
percentUsersBornInDataset?: number;
|
|
140
|
-
/** Bias
|
|
161
|
+
/** 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
162
|
bornRecentBias?: number;
|
|
163
|
+
/** 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" */
|
|
164
|
+
preExistingSpread?: "pinned" | "uniform";
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
export type SCDProp = {
|
|
@@ -183,6 +206,43 @@ export type SoupConfig = {
|
|
|
183
206
|
*/
|
|
184
207
|
type soup = SoupPreset | SoupConfig;
|
|
185
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Macro preset names for big-picture trend shape across the dataset window.
|
|
211
|
+
* Macro is orthogonal to soup: macro shapes the whole-window trend (births,
|
|
212
|
+
* growth, decline); soup shapes the intra-week and intra-day rhythm.
|
|
213
|
+
*/
|
|
214
|
+
export type MacroPreset = "flat" | "steady" | "growth" | "viral" | "decline";
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Macro configuration object — fine-grained big-picture trend control.
|
|
218
|
+
*/
|
|
219
|
+
export type MacroConfig = {
|
|
220
|
+
/** Use a named macro preset as the base, then override individual fields. */
|
|
221
|
+
preset?: MacroPreset;
|
|
222
|
+
/** Bias for birth dates. -1..1; negative = early skew, positive = recent skew, 0 = uniform. */
|
|
223
|
+
bornRecentBias?: number;
|
|
224
|
+
/** Percentage of users born in dataset window (0..100). */
|
|
225
|
+
percentUsersBornInDataset?: number;
|
|
226
|
+
/** "pinned" = pre-existing users stack at FIXED_BEGIN; "uniform" = spread across [FIXED_BEGIN-30d, FIXED_BEGIN]. */
|
|
227
|
+
preExistingSpread?: "pinned" | "uniform";
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/** Big-picture trend shape: preset string, config object, or preset+overrides. */
|
|
231
|
+
type macro = MacroPreset | MacroConfig;
|
|
232
|
+
|
|
233
|
+
/** Public alias for the `soup` config union (preset string or config object). */
|
|
234
|
+
export type Soup = soup;
|
|
235
|
+
|
|
236
|
+
/** Public alias for the `macro` config union (preset string or config object). */
|
|
237
|
+
export type Macro = macro;
|
|
238
|
+
|
|
239
|
+
/** Resolved macro values after preset + override resolution. Used internally. */
|
|
240
|
+
export interface ResolvedMacro {
|
|
241
|
+
bornRecentBias: number;
|
|
242
|
+
percentUsersBornInDataset: number;
|
|
243
|
+
preExistingSpread: "pinned" | "uniform";
|
|
244
|
+
}
|
|
245
|
+
|
|
186
246
|
/**
|
|
187
247
|
* Hook types and when they fire (in order per user):
|
|
188
248
|
* - "user" — user profile object (mutate in-place, return ignored)
|
|
@@ -213,31 +273,141 @@ export type hookTypes =
|
|
|
213
273
|
|
|
214
274
|
/**
|
|
215
275
|
* A hook function that receives every piece of data as it flows through the pipeline.
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
276
|
+
*
|
|
277
|
+
* The runtime signature is intentionally permissive (`any`) because `record` and `meta`
|
|
278
|
+
* vary by `type`. Use the `HookMeta*` interfaces below as convenience types when narrowing
|
|
279
|
+
* inside your hook (e.g. `if (type === "event") { const m = meta as HookMetaEvent; ... }`).
|
|
280
|
+
*
|
|
281
|
+
* Return-value semantics:
|
|
282
|
+
* - "event": return value REPLACES the event (must be the event object).
|
|
283
|
+
* - "everything": return an array to REPLACE the user's event list (filter/inject/dedupe).
|
|
284
|
+
* - "user", "scd-pre", "funnel-pre", "funnel-post": return value is IGNORED — mutate in place.
|
|
285
|
+
* - storage-only ("ad-spend", "group", "mirror", "lookup"): return value is IGNORED.
|
|
286
|
+
*
|
|
287
|
+
* @param record - The data being processed (event, profile, array of events, funnel config, etc.).
|
|
288
|
+
* @param type - Which hook type is firing — see `hookTypes`.
|
|
289
|
+
* @param meta - Contextual metadata. Shape depends on `type` — see `HookMeta*` interfaces.
|
|
219
290
|
*/
|
|
220
291
|
export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
|
|
221
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Time-window anchors present on every hook's `meta`. Use these to derive relative
|
|
295
|
+
* dates inside hooks (e.g. `dayjs.unix(meta.datasetStart).add(45, 'days')`). NEVER
|
|
296
|
+
* read wall-clock `dayjs()` inside a hook — it makes hooks non-deterministic.
|
|
297
|
+
*/
|
|
298
|
+
export interface HookMetaTimeAnchors {
|
|
299
|
+
/** Start of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
|
|
300
|
+
datasetStart: number;
|
|
301
|
+
/** End of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
|
|
302
|
+
datasetEnd: number;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Meta passed to the "event" hook. */
|
|
306
|
+
export interface HookMetaEvent extends HookMetaTimeAnchors {
|
|
307
|
+
/** The user this event belongs to (only `distinct_id` is guaranteed). */
|
|
308
|
+
user: { distinct_id: string };
|
|
309
|
+
/** The fully-resolved dungeon config. */
|
|
310
|
+
config: Dungeon;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Meta passed to the "user" hook (fires when a user profile is created). */
|
|
314
|
+
export interface HookMetaUser extends HookMetaTimeAnchors {
|
|
315
|
+
/** The user object being constructed (mutate in place). */
|
|
316
|
+
user: UserProfile;
|
|
317
|
+
/** The fully-resolved dungeon config. */
|
|
318
|
+
config: Dungeon;
|
|
319
|
+
/** True if the user's account creation falls inside the dataset window. */
|
|
320
|
+
userIsBornInDataset: boolean;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Meta passed to the "scd-pre" hook (fires per SCD prop, before insertion). */
|
|
324
|
+
export interface HookMetaScdPre extends HookMetaTimeAnchors {
|
|
325
|
+
/** The user profile that owns these SCD entries. */
|
|
326
|
+
profile: UserProfile;
|
|
327
|
+
/** The SCD prop key being generated (e.g. "plan", "tier"). */
|
|
328
|
+
type: string;
|
|
329
|
+
/** The full SCD entry list for this prop (mutate in place). */
|
|
330
|
+
scd: SCDSchema[];
|
|
331
|
+
/** The fully-resolved dungeon config. */
|
|
332
|
+
config: Dungeon;
|
|
333
|
+
/** All SCD prop arrays generated so far for this user, keyed by prop name. */
|
|
334
|
+
allSCDs: Record<string, SCDSchema[]>;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Meta passed to the "funnel-pre" hook (mutate funnel before generating events). */
|
|
338
|
+
export interface HookMetaFunnelPre extends HookMetaTimeAnchors {
|
|
339
|
+
user: { distinct_id: string };
|
|
340
|
+
profile: UserProfile;
|
|
341
|
+
scd: Record<string, SCDSchema[]>;
|
|
342
|
+
funnel: Funnel;
|
|
343
|
+
config: Dungeon;
|
|
344
|
+
/** Unix seconds — earliest possible event time for this funnel's first step. */
|
|
345
|
+
firstEventTime: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Meta passed to the "funnel-post" hook (mutate generated funnel events in place). */
|
|
349
|
+
export interface HookMetaFunnelPost extends HookMetaTimeAnchors {
|
|
350
|
+
user: { distinct_id: string };
|
|
351
|
+
profile: UserProfile;
|
|
352
|
+
scd: Record<string, SCDSchema[]>;
|
|
353
|
+
funnel: Funnel;
|
|
354
|
+
config: Dungeon;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Meta passed to the "everything" hook — most powerful hook (sees all events for one user). */
|
|
358
|
+
export interface HookMetaEverything extends HookMetaTimeAnchors {
|
|
359
|
+
/** The user's profile, including merged persona/region/attribution properties. */
|
|
360
|
+
profile: UserProfile;
|
|
361
|
+
/** All SCD entries for this user, keyed by prop name. */
|
|
362
|
+
scd: Record<string, SCDSchema[]>;
|
|
363
|
+
/** The fully-resolved dungeon config. */
|
|
364
|
+
config: Dungeon;
|
|
365
|
+
/** True if the user's account creation falls inside the dataset window. */
|
|
366
|
+
userIsBornInDataset: boolean;
|
|
367
|
+
}
|
|
368
|
+
|
|
222
369
|
export interface hookArrayOptions<T> {
|
|
370
|
+
/** Transform/validate function applied to every record on push. */
|
|
223
371
|
hook?: Hook<T>;
|
|
372
|
+
/** What this array stores — controls hook-firing semantics in the storage layer. */
|
|
224
373
|
type?: hookTypes;
|
|
374
|
+
/** Output filename (no extension; format adds it). Used by storage's batch writer. */
|
|
225
375
|
filename?: string;
|
|
376
|
+
/** Output filepath used when writing batches to disk. */
|
|
377
|
+
filepath?: string;
|
|
378
|
+
/** Output serialization format. */
|
|
226
379
|
format?: "csv" | "json" | "parquet" | string;
|
|
380
|
+
/** Max parallel disk writes. */
|
|
227
381
|
concurrency?: number;
|
|
382
|
+
/** Generation context (config, runtime, defaults). */
|
|
228
383
|
context?: Context;
|
|
229
|
-
[key: string]: any;
|
|
230
384
|
}
|
|
231
385
|
|
|
232
386
|
/**
|
|
233
|
-
* an enriched array is an array that has a hookPush method that can be used to transform-then-push items into the array
|
|
387
|
+
* an enriched array is an array that has a hookPush method that can be used to transform-then-push items into the array.
|
|
388
|
+
*
|
|
389
|
+
* Storage callers also tag the array with a key identifying what it stores
|
|
390
|
+
* (e.g. SCD prop name, group key, lookup table key). The fields are optional
|
|
391
|
+
* because not every HookedArray needs them; mixpanel-sender / user-loop read
|
|
392
|
+
* them when present to route uploads correctly.
|
|
234
393
|
*/
|
|
235
394
|
export interface HookedArray<T> extends Array<T> {
|
|
236
|
-
|
|
395
|
+
/** Transform-then-push. Resolves once the item (and any auto-flushed batch) is persisted. */
|
|
396
|
+
hookPush: (item: T | T[], ...meta: unknown[]) => Promise<void>;
|
|
397
|
+
/** Force-flush any pending batch to disk. */
|
|
237
398
|
flush: () => Promise<void>;
|
|
399
|
+
/** Absolute path of the directory batches will be written to. */
|
|
238
400
|
getWriteDir: () => string;
|
|
401
|
+
/** Absolute path (with extension) of the next batch file. */
|
|
239
402
|
getWritePath: () => string;
|
|
240
|
-
|
|
403
|
+
/** SCD prop name this array carries (only set on SCD HookedArrays). */
|
|
404
|
+
scdKey?: string;
|
|
405
|
+
/** Entity type for SCDs ("user" or a group key). */
|
|
406
|
+
entityType?: string;
|
|
407
|
+
/** Group key this array carries (only set on group profile HookedArrays). */
|
|
408
|
+
groupKey?: string;
|
|
409
|
+
/** Lookup table key this array carries (only set on lookup table HookedArrays). */
|
|
410
|
+
lookupKey?: string;
|
|
241
411
|
}
|
|
242
412
|
|
|
243
413
|
export type AllData =
|
|
@@ -245,8 +415,7 @@ export type AllData =
|
|
|
245
415
|
| HookedArray<UserProfile>
|
|
246
416
|
| HookedArray<GroupProfileSchema>
|
|
247
417
|
| HookedArray<LookupTableSchema>
|
|
248
|
-
| HookedArray<SCDSchema
|
|
249
|
-
| any[];
|
|
418
|
+
| HookedArray<SCDSchema>;
|
|
250
419
|
|
|
251
420
|
/**
|
|
252
421
|
* the storage object is a key-value store that holds arrays of data
|
|
@@ -277,16 +446,33 @@ export interface RuntimeState {
|
|
|
277
446
|
/**
|
|
278
447
|
* Default data factories for generating realistic test data
|
|
279
448
|
*/
|
|
449
|
+
/**
|
|
450
|
+
* Default data factories — pre-resolved at context creation time so user-loop
|
|
451
|
+
* doesn't re-evaluate weighted picker arrays on every iteration.
|
|
452
|
+
*/
|
|
280
453
|
export interface Defaults {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
454
|
+
/** Location pools applied to user profiles (city, region, country, lat/lng). */
|
|
455
|
+
locationsUsers: () => Record<string, ValueValid>[];
|
|
456
|
+
/** Location pools applied to events. */
|
|
457
|
+
locationsEvents: () => Record<string, ValueValid>[];
|
|
458
|
+
/** iOS device pool (model, os version, etc.). */
|
|
459
|
+
iOSDevices: () => Record<string, ValueValid>[];
|
|
460
|
+
/** Android device pool. */
|
|
461
|
+
androidDevices: () => Record<string, ValueValid>[];
|
|
462
|
+
/** Desktop device pool (browser, screen resolution, etc.). */
|
|
463
|
+
desktopDevices: () => Record<string, ValueValid>[];
|
|
464
|
+
/** Browser/UA pool. */
|
|
465
|
+
browsers: () => Record<string, ValueValid>[];
|
|
466
|
+
/** UTM campaign pool used when `hasCampaigns: true`. */
|
|
467
|
+
campaigns: () => Record<string, ValueValid>[];
|
|
468
|
+
/** Pre-built per-platform device arrays selected once at context creation. */
|
|
469
|
+
devicePools: {
|
|
470
|
+
android: Record<string, ValueValid>[];
|
|
471
|
+
ios: Record<string, ValueValid>[];
|
|
472
|
+
desktop: Record<string, ValueValid>[];
|
|
473
|
+
};
|
|
474
|
+
/** Flat union of every device in `devicePools` — used when no platform filter applies. */
|
|
475
|
+
allDevices: Record<string, ValueValid>[];
|
|
290
476
|
}
|
|
291
477
|
|
|
292
478
|
/**
|
|
@@ -297,12 +483,17 @@ export interface Context {
|
|
|
297
483
|
config: Dungeon;
|
|
298
484
|
storage: Storage | null;
|
|
299
485
|
defaults: Defaults;
|
|
300
|
-
|
|
486
|
+
/** Pre-built UTM campaign pool (used when `hasCampaigns: true`). */
|
|
487
|
+
campaigns: Record<string, ValueValid>[];
|
|
301
488
|
runtime: RuntimeState;
|
|
489
|
+
/** End of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetEnd`, or fallback `today_start`. */
|
|
302
490
|
FIXED_NOW: number;
|
|
491
|
+
/** Start of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetStart`, or fallback `today_start - numDays`. */
|
|
303
492
|
FIXED_BEGIN?: number;
|
|
304
|
-
|
|
305
|
-
|
|
493
|
+
/** Alias of `FIXED_BEGIN` — surfaced on hook `meta.datasetStart`. */
|
|
494
|
+
DATASET_START_SECONDS: number;
|
|
495
|
+
/** Alias of `FIXED_NOW` — surfaced on hook `meta.datasetEnd`. */
|
|
496
|
+
DATASET_END_SECONDS: number;
|
|
306
497
|
|
|
307
498
|
// State update methods
|
|
308
499
|
incrementOperations(): void;
|
|
@@ -319,10 +510,6 @@ export interface Context {
|
|
|
319
510
|
incrementUserCount(): void;
|
|
320
511
|
incrementEventCount(): void;
|
|
321
512
|
isBatchMode(): boolean;
|
|
322
|
-
|
|
323
|
-
// Time helper methods
|
|
324
|
-
getTimeShift(): number;
|
|
325
|
-
getDaysShift(): number;
|
|
326
513
|
}
|
|
327
514
|
|
|
328
515
|
/**
|
|
@@ -494,7 +681,8 @@ export interface LookupTableSchema {
|
|
|
494
681
|
|
|
495
682
|
export interface LookupTableData {
|
|
496
683
|
key: string;
|
|
497
|
-
|
|
684
|
+
/** Generated rows for this lookup table. Each row is a flat record keyed by attribute name. */
|
|
685
|
+
data: Record<string, ValueValid>[];
|
|
498
686
|
}
|
|
499
687
|
|
|
500
688
|
export interface SCDSchema {
|
|
@@ -506,7 +694,8 @@ export interface SCDSchema {
|
|
|
506
694
|
|
|
507
695
|
export interface GroupProfileSchema {
|
|
508
696
|
key: string;
|
|
509
|
-
|
|
697
|
+
/** Generated group profile rows. Each row is a flat record keyed by group property name. */
|
|
698
|
+
data: Record<string, ValueValid>[];
|
|
510
699
|
}
|
|
511
700
|
|
|
512
701
|
/**
|
|
@@ -523,15 +712,25 @@ type ImportResult = import("mixpanel-import").ImportResults;
|
|
|
523
712
|
* the end result of the data generation
|
|
524
713
|
*/
|
|
525
714
|
export type Result = {
|
|
715
|
+
/** Generated events. */
|
|
526
716
|
eventData: EventSchema[];
|
|
717
|
+
/** Mirror datasets (transformed copies of `eventData`). */
|
|
527
718
|
mirrorEventData: EventSchema[];
|
|
528
|
-
|
|
529
|
-
|
|
719
|
+
/** User profiles. */
|
|
720
|
+
userProfilesData: UserProfile[];
|
|
721
|
+
/** SCD entries — one inner array per SCD prop. */
|
|
722
|
+
scdTableData: SCDSchema[][];
|
|
723
|
+
/** Ad-spend events (only populated when `hasAdSpend: true`). */
|
|
530
724
|
adSpendData: EventSchema[];
|
|
725
|
+
/** Group profiles — one inner array per group key. */
|
|
531
726
|
groupProfilesData: GroupProfileSchema[][];
|
|
727
|
+
/** Lookup tables — one inner array per table. */
|
|
532
728
|
lookupTableData: LookupTableData[][];
|
|
729
|
+
/** Mixpanel import results (only populated when a token was provided). */
|
|
533
730
|
importResults?: ImportResults;
|
|
731
|
+
/** Absolute paths of all files written to disk. */
|
|
534
732
|
files?: string[];
|
|
733
|
+
/** Timing information. */
|
|
535
734
|
time?: {
|
|
536
735
|
start: number;
|
|
537
736
|
end: number;
|
|
@@ -589,7 +788,7 @@ export interface WorldEvent {
|
|
|
589
788
|
/** Conversion rate modifier during this event. */
|
|
590
789
|
conversionModifier?: number;
|
|
591
790
|
/** Properties injected into affected events. */
|
|
592
|
-
injectProps?: Record<string,
|
|
791
|
+
injectProps?: Record<string, ValueValid>;
|
|
593
792
|
/** Which events are affected ("*" for all, or array of event names). */
|
|
594
793
|
affectsEvents?: string[] | "*";
|
|
595
794
|
/** Aftermath period after the event ends. */
|
|
@@ -752,7 +951,7 @@ export interface GeoRegion {
|
|
|
752
951
|
/** UTC timezone offset for this region (e.g., -5 for EST). */
|
|
753
952
|
timezoneOffset: number;
|
|
754
953
|
/** Properties injected for users in this region. */
|
|
755
|
-
properties?: Record<string,
|
|
954
|
+
properties?: Record<string, ValueValid>;
|
|
756
955
|
}
|
|
757
956
|
|
|
758
957
|
/**
|
|
@@ -792,9 +991,9 @@ export interface FeatureConfig {
|
|
|
792
991
|
/** Property name to inject on events. */
|
|
793
992
|
property: string;
|
|
794
993
|
/** Possible values for the property. First value is the "before" default if defaultBefore not set. */
|
|
795
|
-
values:
|
|
994
|
+
values: ValueValid[];
|
|
796
995
|
/** Default value before the feature launches. If not set, property doesn't exist before launch. */
|
|
797
|
-
defaultBefore?:
|
|
996
|
+
defaultBefore?: ValueValid;
|
|
798
997
|
/** Which events are affected ("*" for all, or array of event names). */
|
|
799
998
|
affectsEvents?: string[] | "*";
|
|
800
999
|
/** Conversion rate lift for users who adopted the feature. */
|
|
@@ -802,7 +1001,7 @@ export interface FeatureConfig {
|
|
|
802
1001
|
/** Resolved logistic curve params (set by config-validator). */
|
|
803
1002
|
_resolvedCurve?: { k: number; midpoint: number };
|
|
804
1003
|
/** Pre-computed adopted values (set by config-validator). */
|
|
805
|
-
_adoptedValues?:
|
|
1004
|
+
_adoptedValues?: ValueValid[];
|
|
806
1005
|
}
|
|
807
1006
|
|
|
808
1007
|
/**
|
|
@@ -830,7 +1029,7 @@ export interface AnomalyConfig {
|
|
|
830
1029
|
/** For burst/coordinated: number of events to inject. */
|
|
831
1030
|
count?: number;
|
|
832
1031
|
/** Properties injected on anomalous events. */
|
|
833
|
-
properties?: Record<string,
|
|
1032
|
+
properties?: Record<string, ValueValid>;
|
|
834
1033
|
/** Resolved absolute start time in unix seconds (set by config-validator). */
|
|
835
1034
|
_startUnix?: number;
|
|
836
1035
|
/** Resolved absolute end time in unix seconds (set by config-validator). */
|
|
@@ -869,8 +1068,8 @@ export declare function loadFromFile(filePath: string): Promise<Dungeon>;
|
|
|
869
1068
|
export declare function loadFromText(code: string): Promise<Dungeon>;
|
|
870
1069
|
/** Parse a JSON dungeon (UI schema format) into a runnable config */
|
|
871
1070
|
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:
|
|
1071
|
+
/** Validate that an object has the minimum shape of a dungeon config. Throws on shape violations. */
|
|
1072
|
+
export declare function validateDungeonShape(config: unknown): void;
|
|
874
1073
|
|
|
875
1074
|
// ============= Text Generator Types =============
|
|
876
1075
|
|
|
@@ -1049,7 +1248,7 @@ export interface TextMetadata {
|
|
|
1049
1248
|
/** Keywords that were injected */
|
|
1050
1249
|
injectedKeywords?: string[];
|
|
1051
1250
|
/** User persona information */
|
|
1052
|
-
persona?: Record<string,
|
|
1251
|
+
persona?: Record<string, ValueValid>;
|
|
1053
1252
|
/** Flesch reading ease score */
|
|
1054
1253
|
readabilityScore?: number;
|
|
1055
1254
|
/** Text style used */
|
|
@@ -1170,13 +1369,14 @@ export interface TimeSoupOptions {
|
|
|
1170
1369
|
}
|
|
1171
1370
|
|
|
1172
1371
|
/**
|
|
1173
|
-
* Test context configuration for unit/integration tests
|
|
1372
|
+
* Test context configuration for unit/integration tests. Looser than `Context`
|
|
1373
|
+
* by design — tests routinely attach ad-hoc fixtures, so the index signature stays.
|
|
1174
1374
|
*/
|
|
1175
1375
|
export interface TestContext {
|
|
1176
1376
|
config: Dungeon;
|
|
1177
1377
|
storage: Storage | null;
|
|
1178
1378
|
defaults: Defaults;
|
|
1179
|
-
campaigns:
|
|
1379
|
+
campaigns: Record<string, ValueValid>[];
|
|
1180
1380
|
runtime: RuntimeState;
|
|
1181
|
-
[key: string]:
|
|
1381
|
+
[key: string]: unknown;
|
|
1182
1382
|
}
|