@ak--47/dungeon-master 1.0.0 → 1.1.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 (103) hide show
  1. package/README.md +7 -7
  2. package/dungeons/technical/ad-spend-schema.json +128 -0
  3. package/dungeons/technical/ad-spend.js +116 -0
  4. package/dungeons/technical/anonymous-users-schema.json +91 -0
  5. package/dungeons/technical/anonymous-users.js +102 -0
  6. package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
  7. package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +11 -4
  8. package/dungeons/technical/experiments-schema.json +197 -0
  9. package/dungeons/technical/experiments.js +168 -0
  10. package/dungeons/technical/foobar-schema.json +362 -0
  11. package/dungeons/{foobar.js → technical/foobar.js} +2 -2
  12. package/dungeons/technical/group-analytics-schema.json +149 -0
  13. package/dungeons/technical/group-analytics.js +151 -0
  14. package/dungeons/technical/mirror-strategies-schema.json +84 -0
  15. package/dungeons/technical/mirror-strategies.js +126 -0
  16. package/dungeons/technical/nested-objects-schema.json +145 -0
  17. package/dungeons/technical/nested-objects.js +207 -0
  18. package/dungeons/technical/retention-cadence-schema.json +37 -0
  19. package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +1 -1
  20. package/dungeons/technical/sanity-schema.json +185 -0
  21. package/dungeons/{sanity.js → technical/sanity.js} +3 -2
  22. package/dungeons/technical/scale-test-schema.json +70 -0
  23. package/dungeons/technical/scale-test.js +90 -0
  24. package/dungeons/technical/scd-schema.json +270 -0
  25. package/dungeons/{scd.js → technical/scd.js} +19 -10
  26. package/dungeons/technical/simple-schema.json +278 -0
  27. package/dungeons/{simple.js → technical/simple.js} +6 -6
  28. package/dungeons/technical/simplest-schema.json +635 -0
  29. package/dungeons/{simplest.js → technical/simplest.js} +6 -6
  30. package/dungeons/technical/text-generation-schema.json +1059 -0
  31. package/dungeons/{text-generation.js → technical/text-generation.js} +23 -9
  32. package/dungeons/vertical/community-schema.json +557 -0
  33. package/dungeons/vertical/community.js +788 -0
  34. package/dungeons/vertical/devtools-schema.json +580 -0
  35. package/dungeons/vertical/devtools.js +837 -0
  36. package/dungeons/vertical/ecommerce-schema.json +586 -0
  37. package/dungeons/{ecommerce.js → vertical/ecommerce.js} +34 -3
  38. package/dungeons/vertical/education-schema.json +697 -0
  39. package/dungeons/{education.js → vertical/education.js} +54 -36
  40. package/dungeons/vertical/fintech-schema.json +563 -0
  41. package/dungeons/{fintech.js → vertical/fintech.js} +91 -53
  42. package/dungeons/vertical/fitness-schema.json +508 -0
  43. package/dungeons/vertical/fitness.js +802 -0
  44. package/dungeons/vertical/food-delivery-schema.json +678 -0
  45. package/dungeons/{food.js → vertical/food-delivery.js} +51 -23
  46. package/dungeons/vertical/gaming-schema.json +394 -0
  47. package/dungeons/{gaming.js → vertical/gaming.js} +9 -7
  48. package/dungeons/vertical/healthcare-schema.json +527 -0
  49. package/dungeons/vertical/healthcare.js +769 -0
  50. package/dungeons/vertical/insurance-application-schema.json +466 -0
  51. package/dungeons/{insurance-application.js → vertical/insurance-application.js} +33 -29
  52. package/dungeons/vertical/logistics-schema.json +552 -0
  53. package/dungeons/vertical/logistics.js +793 -0
  54. package/dungeons/vertical/marketplace-schema.json +511 -0
  55. package/dungeons/vertical/marketplace.js +844 -0
  56. package/dungeons/vertical/media-schema.json +787 -0
  57. package/dungeons/{media.js → vertical/media.js} +94 -16
  58. package/dungeons/vertical/rpg-schema.json +654 -0
  59. package/dungeons/{rpg.js → vertical/rpg.js} +78 -57
  60. package/dungeons/vertical/sass-schema.json +653 -0
  61. package/dungeons/{sass.js → vertical/sass.js} +49 -23
  62. package/dungeons/vertical/social-schema.json +552 -0
  63. package/dungeons/{social.js → vertical/social.js} +60 -14
  64. package/dungeons/vertical/travel-schema.json +558 -0
  65. package/dungeons/vertical/travel.js +736 -0
  66. package/index.js +19 -15
  67. package/lib/core/config-validator.js +286 -1
  68. package/lib/core/context.js +7 -5
  69. package/lib/generators/adspend.js +3 -3
  70. package/lib/generators/events.js +141 -14
  71. package/lib/generators/funnels.js +67 -7
  72. package/lib/generators/text.js +2 -2
  73. package/lib/orchestrators/user-loop.js +441 -22
  74. package/lib/templates/soup-presets.js +2 -2
  75. package/lib/utils/utils.js +4 -5
  76. package/package.json +4 -3
  77. package/scripts/dungeon-to-json.mjs +163 -0
  78. package/scripts/extract-dungeon-schema.mjs +297 -0
  79. package/scripts/json-to-dungeon.mjs +200 -0
  80. package/scripts/run-dungeon.mjs +76 -0
  81. package/scripts/run-many.mjs +118 -0
  82. package/scripts/verify-runner.mjs +42 -0
  83. package/types.d.ts +313 -1
  84. package/dungeons/array-of-object-lookup-schema.json +0 -327
  85. package/dungeons/ecommerce-schema.json +0 -462
  86. package/dungeons/education-schema.json +0 -2409
  87. package/dungeons/fintech-schema.json +0 -14034
  88. package/dungeons/foobar-schema.json +0 -403
  89. package/dungeons/food-delivery-schema.json +0 -192
  90. package/dungeons/food-delivery.js +0 -602
  91. package/dungeons/food-schema.json +0 -1152
  92. package/dungeons/gaming-schema.json +0 -1270
  93. package/dungeons/insurance-application-schema.json +0 -204
  94. package/dungeons/media-schema.json +0 -906
  95. package/dungeons/retention-cadence-schema.json +0 -78
  96. package/dungeons/rpg-schema.json +0 -4526
  97. package/dungeons/sanity-schema.json +0 -255
  98. package/dungeons/sass-schema.json +0 -1291
  99. package/dungeons/scd-schema.json +0 -919
  100. package/dungeons/simple-schema.json +0 -608
  101. package/dungeons/simplest-schema.json +0 -1418
  102. package/dungeons/social-schema.json +0 -1118
  103. package/dungeons/text-generation-schema.json +0 -3096
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Converts a frontend JSON dungeon file to a proper JavaScript dungeon file
5
+ * Usage: node scripts/json-to-dungeon.js <input.json> [output-name]
6
+ */
7
+
8
+ import { readFileSync, writeFileSync } from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+
14
+ // Get command line arguments
15
+ const args = process.argv.slice(2);
16
+
17
+ if (args.length === 0) {
18
+ console.error('❌ Error: Please provide an input JSON file');
19
+ console.log('Usage: node scripts/json-to-dungeon.js <input.json> [output-name]');
20
+ console.log('Example: node scripts/json-to-dungeon.js /path/to/dungeon.json my-dungeon');
21
+ process.exit(1);
22
+ }
23
+
24
+ const inputPath = path.resolve(args[0]);
25
+ const outputName = args[1] || path.basename(inputPath, '.json');
26
+
27
+ try {
28
+ // Read the JSON file
29
+ console.log(`📖 Reading ${inputPath}...`);
30
+ const jsonContent = readFileSync(inputPath, 'utf-8');
31
+ const dungeonData = JSON.parse(jsonContent);
32
+
33
+ // Extract schema and hooks
34
+ const schema = dungeonData.schema || dungeonData;
35
+ const hooksString = dungeonData.hooks || null;
36
+
37
+ // Convert to JavaScript
38
+ console.log('🔄 Converting to JavaScript module...');
39
+ const jsContent = convertToJavaScript(schema, hooksString, outputName);
40
+
41
+ // Write to same directory as input file
42
+ const outputPath = path.join(path.dirname(inputPath), `${outputName}.js`);
43
+ writeFileSync(outputPath, jsContent, 'utf-8');
44
+
45
+ console.log(`✅ Successfully created ${outputPath}`);
46
+ console.log(`\n💡 You can now import and use this dungeon:`);
47
+ console.log(` import config from '${path.relative(process.cwd(), outputPath)}';`);
48
+
49
+ } catch (error) {
50
+ console.error('❌ Error:', error.message);
51
+ process.exit(1);
52
+ }
53
+
54
+ /**
55
+ * Convert JSON schema to JavaScript module string
56
+ */
57
+ function convertToJavaScript(schema, hooksString, name) {
58
+ const lines = [];
59
+
60
+ // File header
61
+ lines.push(`/**`);
62
+ lines.push(` * Generated dungeon: ${name}`);
63
+ lines.push(` * Created: ${new Date().toISOString()}`);
64
+ lines.push(` * Source: Converted from frontend JSON`);
65
+ lines.push(` */`);
66
+ lines.push('');
67
+
68
+ // Imports
69
+ lines.push(`import Chance from 'chance';`);
70
+ lines.push(`let chance = new Chance();`);
71
+ lines.push(`import dayjs from "dayjs";`);
72
+ lines.push(`import utc from "dayjs/plugin/utc.js";`);
73
+ lines.push(`dayjs.extend(utc);`);
74
+ lines.push(`import { uid, comma } from 'ak-tools';`);
75
+ lines.push(`import {
76
+ pickAWinner, weighNumRange, date, integer, weighChoices,
77
+ choose, range, exhaust, maybe, takeSome, randomElement, randomInt,
78
+ weighArray
79
+ } from "../lib/utils/utils.js";`);
80
+ lines.push(`import { createTextGenerator, generateBatch } from "../lib/generators/text.js";`);
81
+ lines.push('');
82
+
83
+ // Config object
84
+ lines.push(`/** @type {import('../types').Dungeon} */`);
85
+ lines.push(`const config = ${convertValueToJS(schema, 1)};`);
86
+ lines.push('');
87
+
88
+ // Hook function (if exists)
89
+ if (hooksString) {
90
+ lines.push(`// Apply the hook function`);
91
+ lines.push(`config.hook = ${hooksString};`);
92
+ lines.push('');
93
+ }
94
+
95
+ // Export
96
+ lines.push(`export default config;`);
97
+ lines.push('');
98
+
99
+ return lines.join('\n');
100
+ }
101
+
102
+ /**
103
+ * Convert a JavaScript value to its source code representation
104
+ */
105
+ function convertValueToJS(value, indentLevel = 0) {
106
+ const indent = '\t'.repeat(indentLevel);
107
+ const nextIndent = '\t'.repeat(indentLevel + 1);
108
+
109
+ // Null
110
+ if (value === null || value === undefined) {
111
+ return 'null';
112
+ }
113
+
114
+ // Boolean
115
+ if (typeof value === 'boolean') {
116
+ return String(value);
117
+ }
118
+
119
+ // Number
120
+ if (typeof value === 'number') {
121
+ return String(value);
122
+ }
123
+
124
+ // String
125
+ if (typeof value === 'string') {
126
+ // Handle multi-line strings
127
+ if (value.includes('\n')) {
128
+ return '`' + value.replace(/`/g, '\\`') + '`';
129
+ }
130
+ // Use double quotes for regular strings
131
+ return '"' + value.replace(/"/g, '\\"') + '"';
132
+ }
133
+
134
+ // Function (stored as object with functionName and args)
135
+ if (value && typeof value === 'object' && value.functionName) {
136
+ const funcName = value.functionName;
137
+ const args = value.args || [];
138
+ const argsStr = args.map(arg => convertValueToJS(arg, 0)).join(', ');
139
+
140
+ // Handle arrow functions
141
+ if (funcName === 'arrow') {
142
+ return value.body || '() => {}';
143
+ }
144
+
145
+ // Handle chance bindings
146
+ if (funcName.startsWith('chance.')) {
147
+ return `${funcName}.bind(chance)`;
148
+ }
149
+
150
+ // Regular function calls
151
+ return `${funcName}(${argsStr})`;
152
+ }
153
+
154
+ // Array
155
+ if (Array.isArray(value)) {
156
+ if (value.length === 0) {
157
+ return '[]';
158
+ }
159
+
160
+ // Check if it's a simple array (all primitives)
161
+ const allPrimitives = value.every(v =>
162
+ v === null ||
163
+ typeof v === 'boolean' ||
164
+ typeof v === 'number' ||
165
+ typeof v === 'string'
166
+ );
167
+
168
+ if (allPrimitives && value.length <= 5) {
169
+ // Inline simple arrays
170
+ const items = value.map(v => convertValueToJS(v, 0));
171
+ return `[${items.join(', ')}]`;
172
+ }
173
+
174
+ // Multi-line arrays
175
+ const items = value.map(v => `${nextIndent}${convertValueToJS(v, indentLevel + 1)}`);
176
+ return `[\n${items.join(',\n')}\n${indent}]`;
177
+ }
178
+
179
+ // Object
180
+ if (typeof value === 'object') {
181
+ const keys = Object.keys(value);
182
+
183
+ if (keys.length === 0) {
184
+ return '{}';
185
+ }
186
+
187
+ const entries = keys.map(key => {
188
+ const val = convertValueToJS(value[key], indentLevel + 1);
189
+ // Use quotes for keys with special characters or spaces
190
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(key) || /^\d/.test(key);
191
+ const keyStr = needsQuotes ? `"${key}"` : key;
192
+ return `${nextIndent}${keyStr}: ${val}`;
193
+ });
194
+
195
+ return `{\n${entries.join(',\n')}\n${indent}}`;
196
+ }
197
+
198
+ // Fallback
199
+ return 'null';
200
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path';
4
+ import generate from '../index.js';
5
+ const { NODE_ENV = "unknown" } = process.env;
6
+
7
+ // Get the dungeon file path from command line arguments
8
+ const dungeonPath = process.argv[2];
9
+
10
+ if (!dungeonPath) {
11
+ console.error('❌ Error: No dungeon file specified');
12
+ console.error('Usage: node run-dungeon.js <path-to-dungeon.js>');
13
+ process.exit(1);
14
+ }
15
+
16
+ // Resolve the absolute path
17
+ const absolutePath = path.isAbsolute(dungeonPath)
18
+ ? dungeonPath
19
+ : path.resolve(process.cwd(), dungeonPath);
20
+
21
+ // Handle Ctrl+C gracefully — let user-loop finish current user, then exit
22
+ process.on('SIGINT', () => {
23
+ // Second Ctrl+C forces immediate exit
24
+ process.on('SIGINT', () => process.exit(1));
25
+ });
26
+
27
+ console.log(`\n🎲 Running dungeon: ${path.basename(absolutePath)}\n`);
28
+
29
+ try {
30
+ // Dynamically import the dungeon configuration
31
+ const { default: config } = await import(absolutePath);
32
+
33
+ if (!config) {
34
+ console.error('❌ Error: Dungeon file must export a default configuration object');
35
+ process.exit(1);
36
+ }
37
+
38
+ // Run the dungeon (always verbose when run locally)
39
+ config.verbose = true;
40
+ const results = await generate(config);
41
+
42
+ // Display results
43
+ console.log('\n✅ Dungeon run complete!');
44
+ console.log('═'.repeat(50));
45
+ console.log(`📊 Results:`);
46
+ console.log(` Users: ${results.userCount.toLocaleString()}`);
47
+ console.log(` Events: ${results.eventCount.toLocaleString()}`);
48
+ if (results.groupCount) {
49
+ console.log(` Groups: ${results.groupCount.toLocaleString()}`);
50
+ }
51
+ if (results.time) {
52
+ console.log(` Duration: ${results.time.human || results.time.delta + 'ms'}`);
53
+ }
54
+
55
+ if (results.avgEPS) {
56
+ console.log(` Avg EPS: ${results.avgEPS.toLocaleString()}`);
57
+ }
58
+
59
+ if (results.files && results.files.length > 0) {
60
+ console.log(`\n📁 Files written:`);
61
+ results.files.forEach(file => {
62
+ console.log(` - ${file}`);
63
+ });
64
+ }
65
+
66
+ console.log('═'.repeat(50) + '\n');
67
+
68
+ // if (NODE_ENV === 'dev') debugger;
69
+
70
+
71
+
72
+ } catch (error) {
73
+ console.error('❌ Error running dungeon:', error.message);
74
+ console.error(error.stack);
75
+ process.exit(1);
76
+ }
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ import { fork } from 'child_process';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import dotenv from "dotenv";
6
+ dotenv.config();
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ import main from "../index.js";
12
+ const CONCURRENCY = 10
13
+
14
+ /**
15
+ * Run multiple dungeons concurrently from the command line.
16
+ *
17
+ * Usage:
18
+ * node scripts/run-many.mjs dungeons/vertical/gaming.js dungeons/vertical/media.js dungeons/vertical/food-delivery.js
19
+ * node scripts/run-many.mjs dungeons/vertical/*.js
20
+ */
21
+
22
+ const args = process.argv.slice(2);
23
+
24
+ if (process.env.__SIM_CHILD) {
25
+ // CHILD: run the assigned dungeon
26
+ const dungeonPath = process.env.__SIM_PATH;
27
+ (async () => {
28
+ try {
29
+ const mod = await import(path.resolve(dungeonPath));
30
+ const spec = mod.default;
31
+ spec.name = path.basename(dungeonPath, '.js');
32
+ spec.verbose = false;
33
+ const result = await main(spec);
34
+ const summary = { eventCount: result.eventCount, userCount: result.userCount, time: result.time };
35
+ if (process.send) process.send({ name: spec.name, success: true, summary }, undefined, undefined, () => process.exit(0));
36
+ else process.exit(0);
37
+ } catch (err) {
38
+ console.error(`[${dungeonPath}] Error:`, err.message);
39
+ if (process.send) process.send({ name: dungeonPath, success: false, error: err.message });
40
+ process.exit(1);
41
+ }
42
+ })();
43
+ } else {
44
+ // PARENT: fork children with concurrency limit
45
+ if (args.length === 0) {
46
+ console.error("Usage: node scripts/run-many.mjs <dungeon1.js> <dungeon2.js> ...");
47
+ process.exit(1);
48
+ }
49
+
50
+ const limit = CONCURRENCY;
51
+ let running = 0;
52
+ const queue = args.map(p => ({ path: p, name: path.basename(p, '.js') }));
53
+ const results = [];
54
+ const total = queue.length;
55
+
56
+ console.log(`\n${"═".repeat(60)}`);
57
+ console.log(`🎲 Running ${total} dungeon(s) with concurrency ${limit}`);
58
+ console.log(`${"═".repeat(60)}\n`);
59
+
60
+ function runOne(dungeon) {
61
+ return new Promise((resolve, reject) => {
62
+ console.log(`▶ Starting: ${dungeon.name}`);
63
+ const startTime = Date.now();
64
+ const child = fork(__filename, [], {
65
+ env: { ...process.env, __SIM_CHILD: "1", __SIM_PATH: dungeon.path },
66
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc']
67
+ });
68
+ child.on('message', (msg) => {
69
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
70
+ if (msg.success) {
71
+ console.log(`✅ Finished: ${dungeon.name} (${elapsed}s)`);
72
+ } else {
73
+ console.error(`❌ Failed: ${dungeon.name} - ${msg.error} (${elapsed}s)`);
74
+ }
75
+ resolve(msg);
76
+ });
77
+ child.on('error', (err) => {
78
+ console.error(`❌ Fork error: ${dungeon.name}`, err.message);
79
+ reject(err);
80
+ });
81
+ child.on('exit', (code) => {
82
+ if (code !== 0 && code !== null) {
83
+ reject(new Error(`${dungeon.name} exited with code ${code}`));
84
+ }
85
+ });
86
+ });
87
+ }
88
+
89
+ function runNext() {
90
+ while (running < limit && queue.length > 0) {
91
+ const dungeon = queue.shift();
92
+ running++;
93
+ runOne(dungeon)
94
+ .then(res => results.push(res))
95
+ .catch(e => results.push({ name: dungeon.name, success: false, error: e.message }))
96
+ .finally(() => { running--; runNext(); });
97
+ }
98
+ }
99
+ runNext();
100
+
101
+ const wait = () => new Promise(resolve => {
102
+ const check = () => {
103
+ if (results.length === total) resolve(results);
104
+ else setTimeout(check, 250);
105
+ };
106
+ check();
107
+ });
108
+
109
+ const all = await wait();
110
+ const succeeded = all.filter(r => r.success).length;
111
+ const failed = all.filter(r => !r.success).length;
112
+
113
+ console.log(`\n${"═".repeat(60)}`);
114
+ console.log(`🏁 Done: ${succeeded} succeeded, ${failed} failed out of ${total}`);
115
+ console.log(`${"═".repeat(60)}\n`);
116
+
117
+ if (failed > 0) process.exit(1);
118
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Verify Runner — runs a dungeon at constrained params for hook verification.
3
+ *
4
+ * Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]
5
+ *
6
+ * Example: node scripts/verify-runner.mjs dungeons/streaming.js verify-streaming
7
+ */
8
+ import generate from '../index.js';
9
+ import path from 'path';
10
+
11
+ const dungeonPath = process.argv[2];
12
+ if (!dungeonPath) {
13
+ console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]');
14
+ process.exit(1);
15
+ }
16
+
17
+ const runName = process.argv[3] || 'verify-hooks';
18
+ const absolutePath = path.isAbsolute(dungeonPath)
19
+ ? dungeonPath
20
+ : path.resolve(process.cwd(), dungeonPath);
21
+
22
+ const { default: config } = await import(absolutePath);
23
+
24
+ const results = await generate({
25
+ ...config,
26
+ token: "",
27
+ numUsers: 1000,
28
+ numEvents: 100_000,
29
+ format: "json",
30
+ gzip: false,
31
+ writeToDisk: true,
32
+ name: runName,
33
+ concurrency: 1,
34
+ verbose: false,
35
+ });
36
+
37
+ console.log(JSON.stringify({
38
+ eventCount: results.eventCount,
39
+ userCount: results.userCount,
40
+ files: results.files,
41
+ duration: results.time?.human || results.time?.delta + 'ms'
42
+ }));