@ak--47/dungeon-master 1.3.0 → 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/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +192 -133
- package/dungeons/vertical/community.js +64 -34
- package/dungeons/vertical/crypto.js +333 -224
- package/dungeons/vertical/dating.js +228 -282
- package/dungeons/vertical/devtools.js +164 -62
- package/dungeons/vertical/ecommerce.js +240 -91
- package/dungeons/vertical/education.js +328 -310
- package/dungeons/vertical/fintech.js +437 -311
- package/dungeons/vertical/fitness.js +141 -58
- package/dungeons/vertical/food-delivery.js +298 -318
- package/dungeons/vertical/gaming.js +378 -231
- package/dungeons/vertical/healthcare.js +140 -60
- package/dungeons/vertical/insurance-application.js +168 -73
- package/dungeons/vertical/logistics.js +95 -6
- package/dungeons/vertical/marketplace.js +137 -51
- package/dungeons/vertical/media.js +241 -386
- package/dungeons/vertical/real-estate.js +322 -315
- package/dungeons/vertical/sass.js +253 -263
- package/dungeons/vertical/social.js +262 -206
- package/dungeons/vertical/travel.js +59 -16
- package/index.js +17 -17
- package/lib/core/config-validator.js +93 -21
- 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 +47 -47
- package/lib/utils/utils.js +71 -39
- package/package.json +1 -1
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +38 -15
- 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/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- 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 -2703
- 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/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smoke-test runner — runs every dungeon at tiny scale in parallel to verify it
|
|
4
|
+
* loads, generates events, and writes output without crashing.
|
|
5
|
+
*
|
|
6
|
+
* Default scale: 100 users, 1000 events per dungeon. NOT for verification —
|
|
7
|
+
* use scripts/verify-runner.mjs at full fidelity for that.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/smoke-test-all.mjs
|
|
11
|
+
* node scripts/smoke-test-all.mjs --dir dungeons/vertical # default
|
|
12
|
+
* node scripts/smoke-test-all.mjs --dir dungeons/technical
|
|
13
|
+
* node scripts/smoke-test-all.mjs --dir dungeons # both subdirs
|
|
14
|
+
* node scripts/smoke-test-all.mjs --concurrency 4 # default: cpu count
|
|
15
|
+
* node scripts/smoke-test-all.mjs --users 500 --events 5000 # override scale
|
|
16
|
+
*
|
|
17
|
+
* Output: per-dungeon PASS/FAIL line + a final summary table. Spawns each
|
|
18
|
+
* dungeon as a child node process so a crash in one doesn't abort the run.
|
|
19
|
+
*/
|
|
20
|
+
import { spawn } from 'child_process';
|
|
21
|
+
import { readdirSync, statSync, rmSync, existsSync, mkdirSync } from 'fs';
|
|
22
|
+
import path from 'path';
|
|
23
|
+
import os from 'os';
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
function arg(name, fallback) {
|
|
27
|
+
const i = args.indexOf(`--${name}`);
|
|
28
|
+
return i === -1 ? fallback : args[i + 1];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
|
32
|
+
const dirArg = arg('dir', 'dungeons/vertical');
|
|
33
|
+
const numUsers = parseInt(arg('users', '100'), 10);
|
|
34
|
+
const numEvents = parseInt(arg('events', '1000'), 10);
|
|
35
|
+
const concurrency = parseInt(arg('concurrency', String(Math.max(2, os.cpus().length))), 10);
|
|
36
|
+
const keep = args.includes('--keep');
|
|
37
|
+
|
|
38
|
+
function findDungeons(dir) {
|
|
39
|
+
const abs = path.resolve(ROOT, dir);
|
|
40
|
+
if (!existsSync(abs)) return [];
|
|
41
|
+
const stat = statSync(abs);
|
|
42
|
+
if (stat.isFile()) return [abs];
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const entry of readdirSync(abs)) {
|
|
45
|
+
const full = path.join(abs, entry);
|
|
46
|
+
const s = statSync(full);
|
|
47
|
+
if (s.isDirectory()) out.push(...findDungeons(full));
|
|
48
|
+
else if (entry.endsWith('.js')) out.push(full);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const dungeons = findDungeons(dirArg).sort();
|
|
54
|
+
if (dungeons.length === 0) {
|
|
55
|
+
console.error(`No dungeons found under ${dirArg}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const dataDir = path.join(ROOT, 'data');
|
|
60
|
+
mkdirSync(dataDir, { recursive: true });
|
|
61
|
+
|
|
62
|
+
const RUNNER = `
|
|
63
|
+
import generate from '${path.join(ROOT, 'index.js')}';
|
|
64
|
+
// With node -e, process.argv = [nodePath, ...userArgs] — no script slot.
|
|
65
|
+
const [dungeonPath, name, numUsers, numEvents] = process.argv.slice(1);
|
|
66
|
+
const r = await generate(dungeonPath, {
|
|
67
|
+
numUsers: parseInt(numUsers, 10),
|
|
68
|
+
numEvents: parseInt(numEvents, 10),
|
|
69
|
+
avgEventsPerUserPerDay: undefined, // force numEvents path
|
|
70
|
+
writeToDisk: true,
|
|
71
|
+
name,
|
|
72
|
+
format: 'json',
|
|
73
|
+
verbose: false,
|
|
74
|
+
concurrency: 1,
|
|
75
|
+
token: '',
|
|
76
|
+
serviceAccount: 'fake', serviceSecret: 'fake', projectId: '1',
|
|
77
|
+
});
|
|
78
|
+
console.log(JSON.stringify({ eventCount: r.eventCount, userCount: r.userCount }));
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
function runOne(dungeonPath) {
|
|
82
|
+
const base = path.basename(dungeonPath, '.js');
|
|
83
|
+
const namePrefix = `smoke-${base}`;
|
|
84
|
+
return new Promise(resolve => {
|
|
85
|
+
const start = Date.now();
|
|
86
|
+
const child = spawn(
|
|
87
|
+
process.execPath,
|
|
88
|
+
['--input-type=module', '-e', RUNNER, dungeonPath, namePrefix, String(numUsers), String(numEvents)],
|
|
89
|
+
{ cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'] }
|
|
90
|
+
);
|
|
91
|
+
let out = '';
|
|
92
|
+
let err = '';
|
|
93
|
+
child.stdout.on('data', d => { out += d.toString(); });
|
|
94
|
+
child.stderr.on('data', d => { err += d.toString(); });
|
|
95
|
+
child.on('close', code => {
|
|
96
|
+
const ms = Date.now() - start;
|
|
97
|
+
let result;
|
|
98
|
+
try {
|
|
99
|
+
const lastLine = out.trim().split('\n').filter(Boolean).pop() || '{}';
|
|
100
|
+
result = JSON.parse(lastLine);
|
|
101
|
+
} catch {
|
|
102
|
+
result = {};
|
|
103
|
+
}
|
|
104
|
+
const ok = code === 0 && result.eventCount > 0;
|
|
105
|
+
if (!keep) {
|
|
106
|
+
try {
|
|
107
|
+
for (const f of readdirSync(dataDir)) {
|
|
108
|
+
if (f.startsWith(`${namePrefix}-`)) rmSync(path.join(dataDir, f));
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
resolve({
|
|
113
|
+
dungeon: path.relative(ROOT, dungeonPath),
|
|
114
|
+
ok,
|
|
115
|
+
code,
|
|
116
|
+
ms,
|
|
117
|
+
eventCount: result.eventCount || 0,
|
|
118
|
+
userCount: result.userCount || 0,
|
|
119
|
+
err: ok ? '' : (err.trim().split('\n').slice(-3).join(' | ') || `exit ${code}`),
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`Smoke test: ${dungeons.length} dungeons, ${numUsers} users / ${numEvents} events each, concurrency=${concurrency}`);
|
|
126
|
+
const queue = [...dungeons];
|
|
127
|
+
const results = [];
|
|
128
|
+
const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
129
|
+
while (queue.length) {
|
|
130
|
+
const d = queue.shift();
|
|
131
|
+
const res = await runOne(d);
|
|
132
|
+
const tag = res.ok ? 'PASS' : 'FAIL';
|
|
133
|
+
console.log(` [${tag}] ${path.basename(res.dungeon)} — ${res.eventCount} events, ${res.userCount} users, ${(res.ms / 1000).toFixed(2)}s${res.ok ? '' : ` — ${res.err}`}`);
|
|
134
|
+
results.push(res);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
await Promise.all(workers);
|
|
138
|
+
|
|
139
|
+
results.sort((a, b) => a.dungeon.localeCompare(b.dungeon));
|
|
140
|
+
const fails = results.filter(r => !r.ok);
|
|
141
|
+
|
|
142
|
+
console.log('\n┌─────────────────────────────────────────────────┬───────┬────────┬───────┬──────┐');
|
|
143
|
+
console.log('│ Dungeon │ State │ Events │ Users │ ms │');
|
|
144
|
+
console.log('├─────────────────────────────────────────────────┼───────┼────────┼───────┼──────┤');
|
|
145
|
+
for (const r of results) {
|
|
146
|
+
const name = r.dungeon.padEnd(47).slice(0, 47);
|
|
147
|
+
const state = (r.ok ? 'PASS' : 'FAIL').padEnd(5);
|
|
148
|
+
const ev = String(r.eventCount).padStart(6);
|
|
149
|
+
const u = String(r.userCount).padStart(5);
|
|
150
|
+
const ms = String(r.ms).padStart(4);
|
|
151
|
+
console.log(`│ ${name} │ ${state} │ ${ev} │ ${u} │ ${ms} │`);
|
|
152
|
+
}
|
|
153
|
+
console.log('└─────────────────────────────────────────────────┴───────┴────────┴───────┴──────┘');
|
|
154
|
+
|
|
155
|
+
console.log(`\n${results.length - fails.length}/${results.length} passed`);
|
|
156
|
+
if (fails.length > 0) {
|
|
157
|
+
console.log('\nFailures:');
|
|
158
|
+
for (const f of fails) {
|
|
159
|
+
console.log(` ✗ ${f.dungeon}: ${f.err}`);
|
|
160
|
+
}
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
@@ -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
|
@@ -21,11 +21,22 @@ export interface Dungeon {
|
|
|
21
21
|
token?: string;
|
|
22
22
|
/** RNG seed for reproducible output. Same seed + concurrency=1 = identical data. */
|
|
23
23
|
seed?: string;
|
|
24
|
-
/** 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). */
|
|
25
25
|
numDays?: number;
|
|
26
|
-
/**
|
|
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`. */
|
|
27
38
|
epochStart?: number;
|
|
28
|
-
/**
|
|
39
|
+
/** @deprecated Legacy alias internally aliased to datasetEnd on validated config. Prefer `datasetEnd`. */
|
|
29
40
|
epochEnd?: number;
|
|
30
41
|
/** Target total number of events to generate across all users. Fallback when avgEventsPerUserPerDay is not set; otherwise derived from rate × numUsers × numDays. */
|
|
31
42
|
numEvents?: number;
|
|
@@ -279,8 +290,20 @@ export type hookTypes =
|
|
|
279
290
|
*/
|
|
280
291
|
export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
|
|
281
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
|
+
|
|
282
305
|
/** Meta passed to the "event" hook. */
|
|
283
|
-
export interface HookMetaEvent {
|
|
306
|
+
export interface HookMetaEvent extends HookMetaTimeAnchors {
|
|
284
307
|
/** The user this event belongs to (only `distinct_id` is guaranteed). */
|
|
285
308
|
user: { distinct_id: string };
|
|
286
309
|
/** The fully-resolved dungeon config. */
|
|
@@ -288,7 +311,7 @@ export interface HookMetaEvent {
|
|
|
288
311
|
}
|
|
289
312
|
|
|
290
313
|
/** Meta passed to the "user" hook (fires when a user profile is created). */
|
|
291
|
-
export interface HookMetaUser {
|
|
314
|
+
export interface HookMetaUser extends HookMetaTimeAnchors {
|
|
292
315
|
/** The user object being constructed (mutate in place). */
|
|
293
316
|
user: UserProfile;
|
|
294
317
|
/** The fully-resolved dungeon config. */
|
|
@@ -298,7 +321,7 @@ export interface HookMetaUser {
|
|
|
298
321
|
}
|
|
299
322
|
|
|
300
323
|
/** Meta passed to the "scd-pre" hook (fires per SCD prop, before insertion). */
|
|
301
|
-
export interface HookMetaScdPre {
|
|
324
|
+
export interface HookMetaScdPre extends HookMetaTimeAnchors {
|
|
302
325
|
/** The user profile that owns these SCD entries. */
|
|
303
326
|
profile: UserProfile;
|
|
304
327
|
/** The SCD prop key being generated (e.g. "plan", "tier"). */
|
|
@@ -312,7 +335,7 @@ export interface HookMetaScdPre {
|
|
|
312
335
|
}
|
|
313
336
|
|
|
314
337
|
/** Meta passed to the "funnel-pre" hook (mutate funnel before generating events). */
|
|
315
|
-
export interface HookMetaFunnelPre {
|
|
338
|
+
export interface HookMetaFunnelPre extends HookMetaTimeAnchors {
|
|
316
339
|
user: { distinct_id: string };
|
|
317
340
|
profile: UserProfile;
|
|
318
341
|
scd: Record<string, SCDSchema[]>;
|
|
@@ -323,7 +346,7 @@ export interface HookMetaFunnelPre {
|
|
|
323
346
|
}
|
|
324
347
|
|
|
325
348
|
/** Meta passed to the "funnel-post" hook (mutate generated funnel events in place). */
|
|
326
|
-
export interface HookMetaFunnelPost {
|
|
349
|
+
export interface HookMetaFunnelPost extends HookMetaTimeAnchors {
|
|
327
350
|
user: { distinct_id: string };
|
|
328
351
|
profile: UserProfile;
|
|
329
352
|
scd: Record<string, SCDSchema[]>;
|
|
@@ -332,7 +355,7 @@ export interface HookMetaFunnelPost {
|
|
|
332
355
|
}
|
|
333
356
|
|
|
334
357
|
/** Meta passed to the "everything" hook — most powerful hook (sees all events for one user). */
|
|
335
|
-
export interface HookMetaEverything {
|
|
358
|
+
export interface HookMetaEverything extends HookMetaTimeAnchors {
|
|
336
359
|
/** The user's profile, including merged persona/region/attribution properties. */
|
|
337
360
|
profile: UserProfile;
|
|
338
361
|
/** All SCD entries for this user, keyed by prop name. */
|
|
@@ -463,10 +486,14 @@ export interface Context {
|
|
|
463
486
|
/** Pre-built UTM campaign pool (used when `hasCampaigns: true`). */
|
|
464
487
|
campaigns: Record<string, ValueValid>[];
|
|
465
488
|
runtime: RuntimeState;
|
|
489
|
+
/** End of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetEnd`, or fallback `today_start`. */
|
|
466
490
|
FIXED_NOW: number;
|
|
491
|
+
/** Start of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetStart`, or fallback `today_start - numDays`. */
|
|
467
492
|
FIXED_BEGIN?: number;
|
|
468
|
-
|
|
469
|
-
|
|
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;
|
|
470
497
|
|
|
471
498
|
// State update methods
|
|
472
499
|
incrementOperations(): void;
|
|
@@ -483,10 +510,6 @@ export interface Context {
|
|
|
483
510
|
incrementUserCount(): void;
|
|
484
511
|
incrementEventCount(): void;
|
|
485
512
|
isBatchMode(): boolean;
|
|
486
|
-
|
|
487
|
-
// Time helper methods
|
|
488
|
-
getTimeShift(): number;
|
|
489
|
-
getDaysShift(): number;
|
|
490
513
|
}
|
|
491
514
|
|
|
492
515
|
/**
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"events": [
|
|
3
|
-
{
|
|
4
|
-
"event": "sign up",
|
|
5
|
-
"isFirstEvent": true,
|
|
6
|
-
"properties": {
|
|
7
|
-
"method": [
|
|
8
|
-
"email",
|
|
9
|
-
"google",
|
|
10
|
-
"facebook",
|
|
11
|
-
"github"
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"event": "page view",
|
|
17
|
-
"weight": 10,
|
|
18
|
-
"properties": {
|
|
19
|
-
"page": [
|
|
20
|
-
"/",
|
|
21
|
-
"/pricing",
|
|
22
|
-
"/features",
|
|
23
|
-
"/docs",
|
|
24
|
-
"/blog",
|
|
25
|
-
"/signup"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"event": "purchase",
|
|
31
|
-
"weight": 2,
|
|
32
|
-
"properties": {
|
|
33
|
-
"amount": {
|
|
34
|
-
"$range": [
|
|
35
|
-
108,
|
|
36
|
-
392
|
|
37
|
-
]
|
|
38
|
-
},
|
|
39
|
-
"currency": [
|
|
40
|
-
"USD",
|
|
41
|
-
"EUR",
|
|
42
|
-
"GBP"
|
|
43
|
-
],
|
|
44
|
-
"item_count": {
|
|
45
|
-
"$range": [
|
|
46
|
-
2,
|
|
47
|
-
7
|
|
48
|
-
]
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"event": "ad click",
|
|
54
|
-
"weight": 6,
|
|
55
|
-
"properties": {
|
|
56
|
-
"partner": [
|
|
57
|
-
"google",
|
|
58
|
-
"facebook",
|
|
59
|
-
"linkedin",
|
|
60
|
-
"twitter",
|
|
61
|
-
"bing",
|
|
62
|
-
"tiktok"
|
|
63
|
-
],
|
|
64
|
-
"ad_format": [
|
|
65
|
-
"banner",
|
|
66
|
-
"video",
|
|
67
|
-
"carousel",
|
|
68
|
-
"native",
|
|
69
|
-
"search"
|
|
70
|
-
],
|
|
71
|
-
"placement": [
|
|
72
|
-
"feed",
|
|
73
|
-
"sidebar",
|
|
74
|
-
"header",
|
|
75
|
-
"interstitial"
|
|
76
|
-
]
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"event": "search",
|
|
81
|
-
"weight": 5,
|
|
82
|
-
"properties": {
|
|
83
|
-
"query_length": {
|
|
84
|
-
"$range": [
|
|
85
|
-
3,
|
|
86
|
-
28
|
|
87
|
-
]
|
|
88
|
-
},
|
|
89
|
-
"results_count": {
|
|
90
|
-
"$range": [
|
|
91
|
-
21,
|
|
92
|
-
79
|
|
93
|
-
]
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"event": "share",
|
|
99
|
-
"weight": 3,
|
|
100
|
-
"properties": {
|
|
101
|
-
"platform": [
|
|
102
|
-
"twitter",
|
|
103
|
-
"facebook",
|
|
104
|
-
"linkedin",
|
|
105
|
-
"email"
|
|
106
|
-
],
|
|
107
|
-
"content_type": [
|
|
108
|
-
"product",
|
|
109
|
-
"page",
|
|
110
|
-
"deal"
|
|
111
|
-
]
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
],
|
|
115
|
-
"userProps": {
|
|
116
|
-
"plan": [
|
|
117
|
-
"free",
|
|
118
|
-
"starter",
|
|
119
|
-
"pro",
|
|
120
|
-
"enterprise"
|
|
121
|
-
],
|
|
122
|
-
"signup_source": [
|
|
123
|
-
"organic",
|
|
124
|
-
"paid",
|
|
125
|
-
"referral"
|
|
126
|
-
]
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"events": [
|
|
3
|
-
{
|
|
4
|
-
"event": "page view",
|
|
5
|
-
"weight": 10,
|
|
6
|
-
"properties": {
|
|
7
|
-
"page": [
|
|
8
|
-
"/",
|
|
9
|
-
"/features",
|
|
10
|
-
"/pricing",
|
|
11
|
-
"/docs",
|
|
12
|
-
"/blog"
|
|
13
|
-
],
|
|
14
|
-
"referrer": [
|
|
15
|
-
"direct",
|
|
16
|
-
"google",
|
|
17
|
-
"twitter",
|
|
18
|
-
"linkedin"
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"event": "signup",
|
|
24
|
-
"isFirstEvent": true,
|
|
25
|
-
"properties": {
|
|
26
|
-
"method": [
|
|
27
|
-
"email",
|
|
28
|
-
"google",
|
|
29
|
-
"github",
|
|
30
|
-
"sso"
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"event": "feature used",
|
|
36
|
-
"weight": 7,
|
|
37
|
-
"properties": {
|
|
38
|
-
"feature": [
|
|
39
|
-
"dashboard",
|
|
40
|
-
"reports",
|
|
41
|
-
"settings",
|
|
42
|
-
"export",
|
|
43
|
-
"import",
|
|
44
|
-
"search"
|
|
45
|
-
]
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"event": "purchase",
|
|
50
|
-
"weight": 2,
|
|
51
|
-
"properties": {
|
|
52
|
-
"amount": {
|
|
53
|
-
"$range": [
|
|
54
|
-
114,
|
|
55
|
-
397
|
|
56
|
-
]
|
|
57
|
-
},
|
|
58
|
-
"plan": [
|
|
59
|
-
"monthly",
|
|
60
|
-
"annual"
|
|
61
|
-
]
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"event": "button click",
|
|
66
|
-
"weight": 8,
|
|
67
|
-
"properties": {
|
|
68
|
-
"button": [
|
|
69
|
-
"cta",
|
|
70
|
-
"nav",
|
|
71
|
-
"menu",
|
|
72
|
-
"submit",
|
|
73
|
-
"cancel",
|
|
74
|
-
"back"
|
|
75
|
-
],
|
|
76
|
-
"location": [
|
|
77
|
-
"header",
|
|
78
|
-
"body",
|
|
79
|
-
"footer",
|
|
80
|
-
"sidebar"
|
|
81
|
-
]
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
],
|
|
85
|
-
"userProps": {
|
|
86
|
-
"plan": [
|
|
87
|
-
"free",
|
|
88
|
-
"pro",
|
|
89
|
-
"enterprise"
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
}
|