make-mp-data 2.0.19 → 2.0.21

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/entry.js CHANGED
@@ -27,14 +27,25 @@ import getCliParams from './lib/cli/cli.js';
27
27
  const result = await main(finalConfig);
28
28
  console.log(`📊 Generated ${(result.eventCount || 0).toLocaleString()} events for ${(result.userCount || 0).toLocaleString()} users`);
29
29
  console.log(`⏱️ Total time: ${result.time?.human || 'unknown'}`);
30
-
30
+ const recordsPerSecond = result.eventCount / result.time.delta * 1000;
31
+ console.log(`⚡ Records per second: ${recordsPerSecond.toFixed(2)}`);
32
+
33
+ if (result.errors?.length) {
34
+ console.error(`\n❗ Errors encountered: ${result.errors.length}`);
35
+ if (cliConfig.verbose) {
36
+ result.errors.forEach(err => console.error(` ${err}`));
37
+ }
38
+ } else {
39
+ console.log(`\n🙌 No errors encountered.`);
40
+ }
41
+
31
42
  if (result.files?.length) {
32
43
  console.log(`📁 Files written: ${result.files.length}`);
33
44
  if (cliConfig.verbose) {
34
45
  result.files.forEach(file => console.log(` ${file}`));
35
46
  }
36
47
  }
37
- console.log(`\n Job completed successfully!`);
48
+ console.log(`\n👋 Job completed successfully!\n`);
38
49
  process.exit(0);
39
50
  } catch (error) {
40
51
  console.error(`\n❌ Job failed: ${error.message}`);
package/index.js CHANGED
@@ -47,6 +47,65 @@ global.FIXED_NOW = FIXED_NOW;
47
47
  let FIXED_BEGIN = dayjs.unix(FIXED_NOW).subtract(90, 'd').unix();
48
48
  global.FIXED_BEGIN = FIXED_BEGIN;
49
49
 
50
+ /**
51
+ * Display configuration summary for CLI mode
52
+ * @param {Config} config - Validated configuration object
53
+ */
54
+ function displayConfigurationSummary(config) {
55
+ console.log('\n📋 Configuration Summary');
56
+ console.log('─'.repeat(40));
57
+
58
+ // Core parameters
59
+ console.log(`🎯 Target: ${config.numUsers?.toLocaleString()} users, ${config.numEvents?.toLocaleString()} events`);
60
+ console.log(`📅 Timeline: ${config.numDays} days (${config.seed ? config.seed : 'random seed'})`);
61
+ console.log(`💾 Output: ${config.format} format${config.region ? ` (${config.region})` : ''}`);
62
+ console.log(`⚡ Performance: ${config.concurrency || 'auto'} threads`);
63
+
64
+ // Feature flags
65
+ const features = [];
66
+ if (config.hasAnonIds) features.push('anonymous IDs');
67
+ if (config.hasSessionIds) features.push('session IDs');
68
+ if (config.alsoInferFunnels) features.push('funnel inference');
69
+ if (config.makeChart) features.push('chart generation');
70
+ if (config.writeToDisk) features.push('disk output');
71
+
72
+ if (features.length > 0) {
73
+ console.log(`🔧 Features: ${features.join(', ')}`);
74
+ }
75
+
76
+ // Schema preview
77
+ if (config.events && config.events.length > 0) {
78
+ console.log('\n🎭 Event Schema');
79
+ console.log('─'.repeat(40));
80
+ const eventNames = config.events.slice(0, 6).map(e => e.event || e).join(', ');
81
+ const more = config.events.length > 6 ? ` (+${config.events.length - 6} more)` : '';
82
+ console.log(`📊 Events: ${eventNames}${more}`);
83
+ }
84
+
85
+ // Funnels preview
86
+ if (config.funnels && config.funnels.length > 0) {
87
+ console.log(`🔄 Funnels: ${config.funnels.length} funnel${config.funnels.length > 1 ? 's' : ''} configured`);
88
+ config.funnels.slice(0, 4).forEach((funnel, i) => {
89
+ if (funnel.sequence) {
90
+ const arrow = ' → ';
91
+ const sequence = funnel.sequence.join(arrow);
92
+ const rate = funnel.conversionRate ? ` (${(funnel.conversionRate * 100).toFixed(0)}% conversion)` : '';
93
+ console.log(` ${i + 1}. ${sequence}${rate}`);
94
+ }
95
+ });
96
+ if (config.funnels.length > 4) {
97
+ console.log(` ...and ${config.funnels.length - 4} more funnels`);
98
+ }
99
+ }
100
+
101
+ // Group analytics
102
+ if (config.groupKeys && config.groupKeys.length > 0) {
103
+ const groups = config.groupKeys.map(([key, count]) => `${count} ${key}s`).join(', ');
104
+ console.log(`👥 Groups: ${groups}`);
105
+ }
106
+
107
+ console.log(''); // Extra spacing before generation starts
108
+ }
50
109
 
51
110
  /**
52
111
  * Main data generation function
@@ -81,6 +140,11 @@ async function main(config) {
81
140
  try {
82
141
  // Step 1: Validate and enrich configuration
83
142
  validatedConfig = validateDungeonConfig(config);
143
+
144
+ // Step 1.5: Display configuration summary (CLI mode only)
145
+ if (isCLI && validatedConfig.verbose) {
146
+ displayConfigurationSummary(validatedConfig);
147
+ }
84
148
 
85
149
  // Step 2: Create context with validated config
86
150
  const context = createContext(validatedConfig, null, isCLI);
@@ -90,6 +154,8 @@ async function main(config) {
90
154
  const storage = await storageManager.initializeContainers();
91
155
  updateContextWithStorage(context, storage);
92
156
 
157
+ // ! DATA GENERATION STARTS HERE
158
+
93
159
  // Step 4: Generate ad spend data (if enabled)
94
160
  if (validatedConfig.hasAdSpend) {
95
161
  await generateAdSpendData(context);
@@ -119,6 +185,10 @@ async function main(config) {
119
185
  await makeMirror(context);
120
186
  }
121
187
 
188
+ if (context.config.verbose) console.log(`\n✅ Data generation completed successfully!\n`);
189
+
190
+ // ! DATA GENERATION ENDS HERE
191
+
122
192
  // Step 10: Generate charts (if enabled)
123
193
  if (validatedConfig.makeChart) {
124
194
  await generateCharts(context);
@@ -252,7 +322,8 @@ async function generateLookupTables(context) {
252
322
 
253
323
  for (let j = 0; j < entries; j++) {
254
324
  const lookupEntry = await makeProfile(context, attributes, {
255
- [key]: `${key}_${j + 1}`
325
+ id: j + 1 //primary key is always a number so it joins simply with events
326
+ // [key]: `${key}_${j + 1}` // we don't want to use the lookup name as a prefix here
256
327
  });
257
328
 
258
329
  await lookupContainer.hookPush(lookupEntry);
@@ -352,7 +423,7 @@ async function generateCharts(context) {
352
423
  if (config.makeChart && storage.eventData?.length > 0) {
353
424
  const chartPath = typeof config.makeChart === 'string'
354
425
  ? config.makeChart
355
- : `./charts/${config.simulationName}-timeline.png`;
426
+ : `./${config.simulationName}-timeline`;
356
427
 
357
428
  await generateLineChart(storage.eventData, undefined, chartPath);
358
429
 
@@ -395,7 +466,7 @@ async function flushStorageToDisk(storage, config) {
395
466
  await Promise.all(flushPromises);
396
467
 
397
468
  if (config.verbose) {
398
- console.log(' Data flushed to disk successfully');
469
+ console.log('🙏 Data flushed to disk successfully');
399
470
  }
400
471
  }
401
472
 
@@ -3,6 +3,11 @@
3
3
  * Extracted from index.js validateDungeonConfig function
4
4
  */
5
5
 
6
+ /** @typedef {import('../../types.js').Dungeon} Dungeon */
7
+ /** @typedef {import('../../types.js').EventConfig} EventConfig */
8
+ /** @typedef {import('../../types.js').Context} Context */
9
+ /** @typedef {import('../../types.js').Funnel} Funnel */
10
+
6
11
  import dayjs from "dayjs";
7
12
  import { makeName } from "ak-tools";
8
13
  import * as u from "../utils/utils.js";
@@ -10,8 +15,8 @@ import os from "os";
10
15
 
11
16
  /**
12
17
  * Infers funnels from the provided events
13
- * @param {Array} events - Array of event configurations
14
- * @returns {Array} Array of inferred funnel configurations
18
+ * @param {EventConfig[]} events - Array of event configurations
19
+ * @returns {Funnel[]} Array of inferred funnel configurations
15
20
  */
16
21
  function inferFunnels(events) {
17
22
  const createdFunnels = [];
@@ -64,8 +69,8 @@ function inferFunnels(events) {
64
69
 
65
70
  /**
66
71
  * Validates and enriches a dungeon configuration object
67
- * @param {Object} config - Raw configuration object
68
- * @returns {Object} Validated and enriched configuration
72
+ * @param {Partial<Dungeon>} config - Raw configuration object
73
+ * @returns {Dungeon} Validated and enriched configuration
69
74
  */
70
75
  export function validateDungeonConfig(config) {
71
76
  const chance = u.getChance();
@@ -111,9 +116,14 @@ export function validateDungeonConfig(config) {
111
116
  alsoInferFunnels = false,
112
117
  name = "",
113
118
  batchSize = 500_000,
114
- concurrency = Math.min(os.cpus().length * 2, 16) // Default to 2x CPU cores, max 16
119
+ concurrency
115
120
  } = config;
116
121
 
122
+ // Set concurrency default only if not provided
123
+ if (concurrency === undefined || concurrency === null) {
124
+ concurrency = Math.min(os.cpus().length * 2, 16);
125
+ }
126
+
117
127
  // Ensure defaults for deep objects
118
128
  if (!config.superProps) config.superProps = superProps;
119
129
  if (!config.userProps || Object.keys(config?.userProps || {})) config.userProps = userProps;
@@ -133,9 +143,9 @@ export function validateDungeonConfig(config) {
133
143
  // Validate events
134
144
  if (!events || !events.length) events = [{ event: "foo" }, { event: "bar" }, { event: "baz" }];
135
145
 
136
- // Convert string events to objects
146
+ // Convert string events to objects
137
147
  if (typeof events[0] === "string") {
138
- events = events.map(e => ({ event: e }));
148
+ events = events.map(e => ({ event: /** @type {string} */ (e) }));
139
149
  }
140
150
 
141
151
  // Handle funnel inference
@@ -233,6 +243,11 @@ export function validateDungeonConfig(config) {
233
243
  * @param {Object} config - Configuration to validate
234
244
  * @throws {Error} If required fields are missing
235
245
  */
246
+ /**
247
+ * Validates required configuration parameters
248
+ * @param {Dungeon} config - Configuration object to validate
249
+ * @returns {boolean} True if validation passes
250
+ */
236
251
  export function validateRequiredConfig(config) {
237
252
  if (!config) {
238
253
  throw new Error("Configuration is required");
@@ -3,14 +3,14 @@
3
3
  * Provides centralized state management and dependency injection
4
4
  */
5
5
 
6
- /** @typedef {import('../../types').Dungeon} Dungeon */
7
- /** @typedef {import('../../types').Storage} Storage */
8
- /** @typedef {import('../../types').Context} Context */
9
- /** @typedef {import('../../types').RuntimeState} RuntimeState */
10
- /** @typedef {import('../../types').Defaults} Defaults */
6
+ /** @typedef {import('../../types.js').Dungeon} Dungeon */
7
+ /** @typedef {import('../../types.js').Storage} Storage */
8
+ /** @typedef {import('../../types.js').Context} Context */
9
+ /** @typedef {import('../../types.js').RuntimeState} RuntimeState */
10
+ /** @typedef {import('../../types.js').Defaults} Defaults */
11
11
 
12
12
  import dayjs from "dayjs";
13
- import { campaigns, devices, locations } from '../data/defaults.js';
13
+ import { campaigns, devices, locations } from '../templates/defaults.js';
14
14
  import * as u from '../utils/utils.js';
15
15
 
16
16
  /**
@@ -31,14 +31,23 @@ function createDefaults(config, campaignData) {
31
31
  locations.filter(l => l.country === singleCountry) :
32
32
  locations;
33
33
 
34
+ // PERFORMANCE: Pre-calculate weighted arrays to avoid repeated weighArray calls
35
+ const weighedLocationsUsers = u.weighArray(locationsUsers);
36
+ const weighedLocationsEvents = u.weighArray(locationsEvents);
37
+ const weighedIOSDevices = u.weighArray(devices.iosDevices);
38
+ const weighedAndroidDevices = u.weighArray(devices.androidDevices);
39
+ const weighedDesktopDevices = u.weighArray(devices.desktopDevices);
40
+ const weighedBrowsers = u.weighArray(devices.browsers);
41
+ const weighedCampaigns = u.weighArray(campaignData);
42
+
34
43
  return {
35
- locationsUsers: () => u.weighArray(locationsUsers),
36
- locationsEvents: () => u.weighArray(locationsEvents),
37
- iOSDevices: () => u.weighArray(devices.iosDevices),
38
- androidDevices: () => u.weighArray(devices.androidDevices),
39
- desktopDevices: () => u.weighArray(devices.desktopDevices),
40
- browsers: () => u.weighArray(devices.browsers),
41
- campaigns: () => u.weighArray(campaignData)
44
+ locationsUsers: () => weighedLocationsUsers,
45
+ locationsEvents: () => weighedLocationsEvents,
46
+ iOSDevices: () => weighedIOSDevices,
47
+ androidDevices: () => weighedAndroidDevices,
48
+ desktopDevices: () => weighedDesktopDevices,
49
+ browsers: () => weighedBrowsers,
50
+ campaigns: () => weighedCampaigns
42
51
  };
43
52
  }
44
53
 
@@ -136,17 +145,23 @@ export function createContext(config, storage = null, isCliMode = null) {
136
145
  // Time helper methods
137
146
  getTimeShift() {
138
147
  const actualNow = dayjs().add(2, "day");
139
- return actualNow.diff(dayjs.unix(global.FIXED_NOW), "seconds");
148
+ return actualNow.diff(dayjs.unix(this.FIXED_NOW), "seconds");
140
149
  },
141
150
 
142
151
  getDaysShift() {
143
152
  const actualNow = dayjs().add(2, "day");
144
- return actualNow.diff(dayjs.unix(global.FIXED_NOW), "days");
153
+ return actualNow.diff(dayjs.unix(this.FIXED_NOW), "days");
145
154
  },
146
155
 
147
156
  // Time constants (previously globals)
148
157
  FIXED_NOW: global.FIXED_NOW,
149
- FIXED_BEGIN: global.FIXED_BEGIN
158
+ FIXED_BEGIN: global.FIXED_BEGIN,
159
+
160
+ // PERFORMANCE: Pre-calculated time shift (instead of calculating per-event)
161
+ TIME_SHIFT_SECONDS: (() => {
162
+ const actualNow = dayjs().add(2, "day");
163
+ return actualNow.diff(dayjs.unix(global.FIXED_NOW), "seconds");
164
+ })(),
150
165
  };
151
166
 
152
167
  return context;
@@ -3,7 +3,10 @@
3
3
  * Extracted from index.js to eliminate global dependencies
4
4
  */
5
5
 
6
- /** @typedef {import('../../types').Context} Context */
6
+ /** @typedef {import('../../types.js').Context} Context */
7
+ /** @typedef {import('../../types.js').HookedArray<any>} HookedArray */
8
+ /** @typedef {import('../../types.js').Storage} Storage */
9
+ /** @typedef {import('../../types.js').hookArrayOptions<any>} hookArrayOptions */
7
10
 
8
11
  import { existsSync } from "fs";
9
12
  import pLimit from 'p-limit';
@@ -14,25 +17,19 @@ import * as u from "../utils/utils.js";
14
17
  /**
15
18
  * Creates a hooked array that transforms data on push and handles batching/disk writes
16
19
  * @param {Array} arr - Base array to enhance
17
- * @param {Object} opts - Configuration options
18
- * @param {Function} opts.hook - Transform function applied to each item
19
- * @param {string} opts.type - Type identifier for the hook function
20
- * @param {string} opts.filepath - Base filename for disk writes
21
- * @param {string} opts.format - Output format ('csv' or 'json')
22
- * @param {number} opts.concurrency - Max concurrent file operations
23
- * @param {Context} opts.context - Context object with config, batchSize, etc.
24
- * @returns {Promise<Array>} Enhanced array with hookPush and flush methods
20
+ * @param {hookArrayOptions} opts - Configuration options
21
+ * @returns {Promise<HookedArray>} Enhanced array with hookPush and flush methods
25
22
  */
26
- export async function createHookArray(arr = [], opts = {}) {
23
+ export async function createHookArray(arr = [], opts) {
27
24
  const {
28
25
  hook = a => a,
29
26
  type = "",
30
27
  filepath = "./defaultFile",
31
28
  format = "csv",
32
29
  concurrency = 1,
33
- context = {},
30
+ context = /** @type {Context} */ ({}),
34
31
  ...rest
35
- } = opts;
32
+ } = opts || {};
36
33
 
37
34
  const FILE_CONN = pLimit(concurrency);
38
35
  const { config = {}, runtime = {} } = context;
@@ -77,7 +74,7 @@ export async function createHookArray(arr = [], opts = {}) {
77
74
 
78
75
  // Performance optimization: skip hook overhead for passthrough hooks
79
76
  const isPassthroughHook = hook.toString().includes('return record') || hook.length === 1;
80
-
77
+
81
78
  if (isPassthroughHook) {
82
79
  // Fast path for passthrough hooks - no transformation needed
83
80
  if (Array.isArray(item)) {
@@ -118,6 +115,8 @@ export async function createHookArray(arr = [], opts = {}) {
118
115
  batch++;
119
116
  const writePath = getWritePath();
120
117
  const writeResult = await FILE_CONN(() => writeToDisk(arr, { writePath }));
118
+ // Ensure array is cleared after successful write
119
+ arr.length = 0;
121
120
  return writeResult;
122
121
  } else {
123
122
  return Promise.resolve(false);
@@ -129,7 +128,7 @@ export async function createHookArray(arr = [], opts = {}) {
129
128
  let writeResult;
130
129
 
131
130
  if (config.verbose) {
132
- console.log(`\n\n\twriting ${writePath}\n\n`);
131
+ console.log(`\n\twriting ${writePath}\n`);
133
132
  }
134
133
 
135
134
  switch (format) {
@@ -143,7 +142,7 @@ export async function createHookArray(arr = [], opts = {}) {
143
142
  throw new Error(`format ${format} is not supported`);
144
143
  }
145
144
 
146
- if (isBatchMode) data.length = 0;
145
+ // Array clearing now handled in transformThenPush to ensure proper timing
147
146
  return writeResult;
148
147
  }
149
148
 
@@ -157,7 +156,8 @@ export async function createHookArray(arr = [], opts = {}) {
157
156
  }
158
157
 
159
158
  // Enhance the array with our methods
160
- const enrichedArray = arr;
159
+ /** @type {HookedArray} */
160
+ const enrichedArray = /** @type {any} */ (arr);
161
161
  enrichedArray.hookPush = transformThenPush;
162
162
  enrichedArray.flush = flush;
163
163
  enrichedArray.getWriteDir = getWriteDir;
@@ -181,11 +181,12 @@ export class StorageManager {
181
181
 
182
182
  /**
183
183
  * Initialize all storage containers for the data generation process
184
- * @returns {import('../../types').Storage} Storage containers object
184
+ * @returns {Promise<Storage>} Storage containers object
185
185
  */
186
186
  async initializeContainers() {
187
187
  const { config } = this.context;
188
188
 
189
+ /** @type {Storage} */
189
190
  const storage = {
190
191
  eventData: await createHookArray([], {
191
192
  hook: config.hook,
@@ -207,7 +208,7 @@ export class StorageManager {
207
208
 
208
209
  adSpendData: await createHookArray([], {
209
210
  hook: config.hook,
210
- type: "adspend",
211
+ type: "ad-spend",
211
212
  filepath: `${config.simulationName || 'adspend'}-ADSPEND`,
212
213
  format: config.format || "csv",
213
214
  concurrency: config.concurrency || 1,
@@ -267,7 +268,7 @@ export class StorageManager {
267
268
  hook: config.hook,
268
269
  type: "lookup",
269
270
  filepath: `${config.simulationName || 'lookup'}-${lookupConfig.key}-LOOKUP`,
270
- format: config.format || "csv",
271
+ format: "csv", // Always force CSV for lookup tables
271
272
  concurrency: config.concurrency || 1,
272
273
  context: this.context
273
274
  });
@@ -102,19 +102,22 @@ export async function makeEvent(
102
102
  // Set event time using TimeSoup for realistic distribution
103
103
  if (earliestTime) {
104
104
  if (isFirstEvent) {
105
- eventTemplate.time = dayjs.unix(earliestTime).toISOString();
105
+ // Apply time shift to move to present day using precomputed value
106
+ eventTemplate.time = dayjs.unix(earliestTime).add(context.TIME_SHIFT_SECONDS, 'seconds').toISOString();
106
107
  } else {
107
- eventTemplate.time = u.TimeSoup(earliestTime, context.FIXED_NOW, peaks, deviation, mean);
108
+ // Get time from TimeSoup and apply precomputed time shift
109
+ const soupTime = u.TimeSoup(earliestTime, context.FIXED_NOW, peaks, deviation, mean);
110
+ eventTemplate.time = dayjs(soupTime).add(context.TIME_SHIFT_SECONDS, 'seconds').toISOString();
108
111
  }
109
112
  }
110
113
 
111
114
  // Add anonymous and session identifiers
112
115
  if (anonymousIds.length) {
113
- eventTemplate.device_id = chance.pickone(anonymousIds);
116
+ eventTemplate.device_id = u.pickRandom(anonymousIds);
114
117
  }
115
118
 
116
119
  if (sessionIds.length) {
117
- eventTemplate.session_id = chance.pickone(sessionIds);
120
+ eventTemplate.session_id = u.pickRandom(sessionIds);
118
121
  }
119
122
 
120
123
  // Sometimes add user_id (for attribution modeling)
@@ -127,16 +130,28 @@ export async function makeEvent(
127
130
  eventTemplate.user_id = distinct_id;
128
131
  }
129
132
 
130
- // Merge custom properties with super properties
131
- const props = Object.assign({}, chosenEvent.properties, superProps);
132
-
133
+ // PERFORMANCE: Process properties directly without creating intermediate object
133
134
  // Add custom properties from event configuration
134
- for (const key in props) {
135
- try {
136
- eventTemplate[key] = u.choose(props[key]);
137
- } catch (e) {
138
- console.error(`error with ${key} in ${chosenEvent.event} event`, e);
139
- // Continue processing other properties
135
+ if (chosenEvent.properties) {
136
+ for (const key in chosenEvent.properties) {
137
+ try {
138
+ eventTemplate[key] = u.choose(chosenEvent.properties[key]);
139
+ } catch (e) {
140
+ console.error(`error with ${key} in ${chosenEvent.event} event`, e);
141
+ // Continue processing other properties
142
+ }
143
+ }
144
+ }
145
+
146
+ // Add super properties (override event properties if needed)
147
+ if (superProps) {
148
+ for (const key in superProps) {
149
+ try {
150
+ eventTemplate[key] = u.choose(superProps[key]);
151
+ } catch (e) {
152
+ console.error(`error with ${key} in super props`, e);
153
+ // Continue processing other properties
154
+ }
140
155
  }
141
156
  }
142
157
 
@@ -153,13 +168,21 @@ export async function makeEvent(
153
168
  const tuple = `${eventTemplate.event}-${eventTemplate.time}-${distinctId}`;
154
169
  eventTemplate.insert_id = u.quickHash(tuple);
155
170
 
156
- // Apply time shift to move events to current timeline
157
- if (earliestTime) {
158
- const timeShift = dayjs().add(2, "day").diff(dayjs.unix(context.FIXED_NOW), "seconds");
159
- const timeShifted = dayjs(eventTemplate.time).add(timeShift, "seconds").toISOString();
160
- eventTemplate.time = timeShifted;
171
+ // Call hook if configured (before returning the event)
172
+ const { hook } = config;
173
+ if (hook) {
174
+ const hookedEvent = await hook(eventTemplate, "event", {
175
+ user: { distinct_id },
176
+ config
177
+ });
178
+ // If hook returns a modified event, use it; otherwise use original
179
+ if (hookedEvent && typeof hookedEvent === 'object') {
180
+ return hookedEvent;
181
+ }
161
182
  }
162
183
 
184
+ // Note: Time shift already applied above during timestamp calculation
185
+
163
186
  return eventTemplate;
164
187
  }
165
188
 
@@ -3,6 +3,9 @@
3
3
  * Handles distributed processing across multiple cloud function workers
4
4
  */
5
5
 
6
+ /** @typedef {import('../../types.js').Context} Context */
7
+ /** @typedef {import('../../types.js').Dungeon} Dungeon */
8
+
6
9
  import pLimit from 'p-limit';
7
10
  import { GoogleAuth } from 'google-auth-library';
8
11
  import { timer, uid, sLog } from 'ak-tools';
@@ -138,7 +141,7 @@ export async function handleCloudFunctionEntry(req, res, mainFunction) {
138
141
  try {
139
142
  if (!script) throw new Error("no script");
140
143
 
141
- /** @type {Config} */
144
+ /** @type {Dungeon} */
142
145
  const config = eval(script);
143
146
 
144
147
  if (isReplica) {
@@ -148,7 +151,7 @@ export async function handleCloudFunctionEntry(req, res, mainFunction) {
148
151
  params.seed = newSeed;
149
152
  }
150
153
 
151
- /** @type {Config} */
154
+ /** @type {Dungeon} */
152
155
  const optionsYouCantChange = {
153
156
  verbose: false
154
157
  };