make-mp-data 2.1.6 → 3.0.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.
Files changed (76) hide show
  1. package/README.md +31 -0
  2. package/dungeons/adspend.js +2 -2
  3. package/dungeons/ai-chat-analytics-ed.js +3 -2
  4. package/dungeons/anon.js +2 -2
  5. package/dungeons/array-of-object-loopup.js +181 -0
  6. package/dungeons/benchmark-heavy.js +241 -0
  7. package/dungeons/benchmark-light.js +141 -0
  8. package/dungeons/big.js +9 -8
  9. package/dungeons/business.js +2 -1
  10. package/dungeons/clinch-agi.js +632 -0
  11. package/dungeons/complex.js +3 -2
  12. package/dungeons/copilot.js +383 -0
  13. package/dungeons/ecommerce-store.js +0 -0
  14. package/dungeons/experiments.js +5 -4
  15. package/dungeons/foobar.js +101 -101
  16. package/dungeons/funnels.js +2 -2
  17. package/dungeons/gaming.js +3 -2
  18. package/dungeons/harness/harness-education.js +988 -0
  19. package/dungeons/harness/harness-fintech.js +976 -0
  20. package/dungeons/harness/harness-food.js +985 -0
  21. package/dungeons/harness/harness-gaming.js +1178 -0
  22. package/dungeons/harness/harness-media.js +961 -0
  23. package/dungeons/harness/harness-sass.js +923 -0
  24. package/dungeons/harness/harness-social.js +928 -0
  25. package/dungeons/kurby.js +211 -0
  26. package/dungeons/media.js +5 -4
  27. package/dungeons/mil.js +4 -3
  28. package/dungeons/mirror.js +2 -2
  29. package/dungeons/money2020-ed.js +8 -7
  30. package/dungeons/sanity.js +3 -2
  31. package/dungeons/scd.js +3 -2
  32. package/dungeons/simple.js +29 -14
  33. package/dungeons/strict-event-test.js +30 -0
  34. package/dungeons/student-teacher.js +3 -2
  35. package/dungeons/text-generation.js +84 -85
  36. package/dungeons/too-big-events.js +166 -0
  37. package/dungeons/uday-schema.json +220 -0
  38. package/dungeons/userAgent.js +4 -3
  39. package/index.js +41 -54
  40. package/lib/core/config-validator.js +122 -7
  41. package/lib/core/context.js +7 -14
  42. package/lib/core/storage.js +60 -30
  43. package/lib/generators/adspend.js +12 -27
  44. package/lib/generators/events.js +6 -7
  45. package/lib/generators/funnels.js +16 -5
  46. package/lib/generators/product-lookup.js +262 -0
  47. package/lib/generators/product-names.js +195 -0
  48. package/lib/generators/profiles.js +3 -3
  49. package/lib/generators/scd.js +13 -3
  50. package/lib/generators/text.js +17 -4
  51. package/lib/orchestrators/mixpanel-sender.js +251 -208
  52. package/lib/orchestrators/user-loop.js +57 -19
  53. package/lib/templates/funnels-instructions.txt +272 -0
  54. package/lib/templates/hook-examples.json +187 -0
  55. package/lib/templates/hooks-instructions.txt +295 -8
  56. package/lib/templates/phrases.js +473 -16
  57. package/lib/templates/refine-instructions.txt +485 -0
  58. package/lib/templates/schema-instructions.txt +239 -109
  59. package/lib/templates/schema.d.ts +173 -0
  60. package/lib/templates/verbose-schema.js +140 -206
  61. package/lib/utils/ai.js +853 -77
  62. package/lib/utils/chart.js +210 -0
  63. package/lib/utils/function-registry.js +285 -0
  64. package/lib/utils/json-evaluator.js +172 -0
  65. package/lib/utils/logger.js +38 -0
  66. package/lib/utils/mixpanel.js +101 -0
  67. package/lib/utils/project.js +3 -2
  68. package/lib/utils/utils.js +41 -4
  69. package/package.json +13 -19
  70. package/types.d.ts +15 -5
  71. package/lib/generators/text-bak-old.js +0 -1121
  72. package/lib/orchestrators/worker-manager.js +0 -203
  73. package/lib/templates/phrases-bak.js +0 -925
  74. package/lib/templates/prompt (old).txt +0 -98
  75. package/lib/templates/scratch-dungeon-template.js +0 -116
  76. package/lib/templates/textQuickTest.js +0 -172
@@ -0,0 +1,210 @@
1
+ /** @typedef {import('../../types.js').EventSchema} EventSchema */
2
+ /** @typedef {import('../../types.js').Result} Result */
3
+ /** @typedef {import('../../types.js').Context} Context */
4
+
5
+ // import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
6
+ import fs from 'fs';
7
+ import * as u from 'ak-tools';
8
+ import dayjs from 'dayjs';
9
+ import { openFinder } from './utils.js';
10
+ import { dataLogger as logger } from './logger.js';
11
+ const { existsSync } = fs;
12
+ import path from 'path';
13
+ import 'dotenv/config';
14
+ const { NODE_ENV = "unknown" } = process.env;
15
+
16
+
17
+ let tempDir;
18
+ const dataFolder = path.resolve("./data");
19
+ if (existsSync(dataFolder)) tempDir = dataFolder;
20
+ else tempDir = path.resolve("./");
21
+
22
+
23
+ // Function to count events per day
24
+ function countDailyEvents(eventData) {
25
+ const dailyCounts = {};
26
+
27
+ eventData.forEach(event => {
28
+ const date = dayjs(event.time).format('YYYY-MM-DD');
29
+ if (!dailyCounts[date]) {
30
+ dailyCounts[date] = 0;
31
+ }
32
+ dailyCounts[date]++;
33
+ });
34
+
35
+ return dailyCounts;
36
+ }
37
+
38
+ // Function to count daily users
39
+ function countDailyUsers(eventData) {
40
+ const dailyUsers = {};
41
+
42
+ eventData.forEach(event => {
43
+ const date = dayjs(event.time).format('YYYY-MM-DD');
44
+ if (!dailyUsers[date]) {
45
+ dailyUsers[date] = new Set();
46
+ }
47
+ dailyUsers[date].add(event.user_id);
48
+ });
49
+
50
+ return dailyUsers;
51
+ }
52
+
53
+ // Function to count daily new users based on signup events
54
+ function countDailyNewUsers(eventData, signupEvents) {
55
+ const dailyNewUsers = {};
56
+ const seenUsers = new Set();
57
+
58
+ eventData.forEach(event => {
59
+ const date = dayjs(event.time).format('YYYY-MM-DD');
60
+ if (!dailyNewUsers[date]) {
61
+ dailyNewUsers[date] = new Set();
62
+ }
63
+ if (signupEvents.includes(event.event) && !seenUsers.has(event.user_id)) {
64
+ dailyNewUsers[date].add(event.user_id);
65
+ seenUsers.add(event.user_id);
66
+ }
67
+ });
68
+
69
+ return dailyNewUsers;
70
+ }
71
+
72
+ // Function to generate line chart
73
+ async function generateLineChart(rawData, signupEvents = ["sign up"], fileName) {
74
+ // COMMENTED OUT: Canvas dependency removed
75
+ throw new Error("Chart generation is temporarily disabled - canvas dependency removed");
76
+
77
+ // const width = 1600;
78
+ // const height = 1200;
79
+ // const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, backgroundColour: 'black' });
80
+ const eventData = countDailyEvents(rawData);
81
+ const userData = countDailyUsers(rawData);
82
+ const newUserData = countDailyNewUsers(rawData, signupEvents);
83
+
84
+ // @ts-ignore
85
+ const sortedEventEntries = Object.entries(eventData).sort((a, b) => new Date(a[0]) - new Date(b[0]));
86
+ const eventLabels = sortedEventEntries.map(entry => entry[0]);
87
+ const eventValues = sortedEventEntries.map(entry => entry[1]);
88
+
89
+ // @ts-ignore
90
+ const sortedUserEntries = Object.entries(userData).sort((a, b) => new Date(a[0]) - new Date(b[0]));
91
+ const userLabels = sortedUserEntries.map(entry => entry[0]);
92
+ const userValues = sortedUserEntries.map(entry => entry[1].size);
93
+
94
+ // @ts-ignore
95
+ const sortedNewUserEntries = Object.entries(newUserData).sort((a, b) => new Date(a[0]) - new Date(b[0]));
96
+ const newUserLabels = sortedNewUserEntries.map(entry => entry[0]);
97
+ const newUserValues = sortedNewUserEntries.map(entry => entry[1].size);
98
+
99
+ const configuration = {
100
+ type: 'line',
101
+ data: {
102
+ labels: eventLabels,
103
+ datasets: [
104
+ {
105
+ label: '# EVENTS',
106
+ data: eventValues,
107
+ yAxisID: 'y1',
108
+ fill: true,
109
+ borderColor: '#4F44E0',
110
+ tension: 0.1
111
+ },
112
+ // {
113
+ // label: '# USERS',
114
+ // data: userValues,
115
+ // yAxisID: 'y2',
116
+ // fill: true,
117
+ // borderColor: '#E34F2F',
118
+ // tension: 0.1
119
+ // },
120
+ {
121
+ label: '# NEW',
122
+ data: newUserValues,
123
+ yAxisID: 'y3',
124
+ fill: true,
125
+ borderColor: '#219464',
126
+ tension: 0.1
127
+ }
128
+ ]
129
+ },
130
+ options: {
131
+ scales: {
132
+ x: {
133
+ title: {
134
+ display: true,
135
+ text: 'Date',
136
+ color: 'white'
137
+ },
138
+ ticks: {
139
+ color: 'white'
140
+ }
141
+ },
142
+ y1: {
143
+ type: 'linear',
144
+ display: true,
145
+ position: 'left',
146
+ title: {
147
+ display: true,
148
+ text: 'Count of Events',
149
+ color: '#4F44E0'
150
+ },
151
+ ticks: {
152
+ color: '#4F44E0'
153
+ }
154
+ },
155
+ y3: {
156
+ type: 'linear',
157
+ display: true,
158
+ position: 'right',
159
+ offset: true,
160
+ title: {
161
+ display: true,
162
+ text: 'Count of New Users',
163
+ color: '#219464'
164
+ },
165
+ ticks: {
166
+ color: '#219464'
167
+ },
168
+ grid: {
169
+ drawOnChartArea: false
170
+ }
171
+ }
172
+ },
173
+ plugins: {
174
+ legend: {
175
+ labels: {
176
+ color: 'white'
177
+ }
178
+ }
179
+ }
180
+ }
181
+ };
182
+
183
+ // @ts-ignore
184
+ if (typeof fileName === undefined) fileName = 'chart';
185
+ if (typeof fileName !== 'string') fileName = 'chart';
186
+ // @ts-ignore
187
+ // const imageBuffer = await chartJSNodeCanvas.renderToBuffer(configuration);
188
+ // const filePath = path.join(tempDir, `${fileName}.png`);
189
+ // const removed = await u.rm(filePath);
190
+ // @ts-ignore - imageBuffer is a Buffer but touch accepts it
191
+ // const file = await u.touch(filePath, imageBuffer);
192
+
193
+ // logger.info({ filename: `${fileName}.png` }, `📊 Chart saved as ${fileName}.png`);
194
+ // openFinder(path)
195
+ // return file;
196
+ }
197
+
198
+ export { generateLineChart };
199
+
200
+
201
+
202
+ if (import.meta.url === `file://${process.argv[1]}`) {
203
+ generateLineChart()
204
+ .then((result)=>{
205
+ if (NODE_ENV === "dev") debugger;
206
+ })
207
+ .catch((error)=>{
208
+ if (NODE_ENV === "dev") debugger;
209
+ })
210
+ }
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Registry of valid functions that can be called in dungeon configurations
3
+ * This replaces the old string-based function parsing with a clean JSON structure
4
+ */
5
+
6
+ import { dataLogger as logger } from './logger.js';
7
+
8
+ export const FUNCTION_REGISTRY = {
9
+ // Utility functions from utils.js
10
+ weighNumRange: {
11
+ minArgs: 2,
12
+ maxArgs: 3,
13
+ description: 'Generate weighted random number in range'
14
+ },
15
+ range: {
16
+ minArgs: 2,
17
+ maxArgs: 2,
18
+ description: 'Generate array of numbers in range'
19
+ },
20
+ date: {
21
+ minArgs: 1,
22
+ maxArgs: 3,
23
+ description: 'Generate date string'
24
+ },
25
+ choose: {
26
+ minArgs: 1,
27
+ maxArgs: 1,
28
+ description: 'Choose from array'
29
+ },
30
+ integer: {
31
+ minArgs: 1,
32
+ maxArgs: 2,
33
+ description: 'Generate random integer'
34
+ },
35
+ exhaust: {
36
+ minArgs: 1,
37
+ maxArgs: 1,
38
+ description: 'Exhaust values from array'
39
+ },
40
+
41
+ // Additional utility functions
42
+ maybe: {
43
+ minArgs: 1,
44
+ maxArgs: 2,
45
+ description: 'Return value or null based on probability'
46
+ },
47
+ takeSome: {
48
+ minArgs: 1,
49
+ maxArgs: 3,
50
+ description: 'Take random subset from array'
51
+ },
52
+ randomElement: {
53
+ minArgs: 1,
54
+ maxArgs: 1,
55
+ description: 'Return random element from array'
56
+ },
57
+ randomInt: {
58
+ minArgs: 2,
59
+ maxArgs: 2,
60
+ description: 'Generate random integer'
61
+ },
62
+
63
+ // Text generation functions
64
+ createTextGenerator: {
65
+ minArgs: 1,
66
+ maxArgs: 1,
67
+ description: 'Create text generator with config'
68
+ },
69
+ generateBatch: {
70
+ minArgs: 2,
71
+ maxArgs: 2,
72
+ description: 'Generate batch of text'
73
+ },
74
+
75
+ // Chance.js functions (using dot notation)
76
+ 'chance.word': {
77
+ minArgs: 0,
78
+ maxArgs: 1,
79
+ description: 'Generate random word'
80
+ },
81
+ 'chance.sentence': {
82
+ minArgs: 0,
83
+ maxArgs: 1,
84
+ description: 'Generate random sentence'
85
+ },
86
+ 'chance.paragraph': {
87
+ minArgs: 0,
88
+ maxArgs: 1,
89
+ description: 'Generate random paragraph'
90
+ },
91
+ 'chance.name': {
92
+ minArgs: 0,
93
+ maxArgs: 1,
94
+ description: 'Generate random name'
95
+ },
96
+ 'chance.first': {
97
+ minArgs: 0,
98
+ maxArgs: 1,
99
+ description: 'Generate random first name'
100
+ },
101
+ 'chance.last': {
102
+ minArgs: 0,
103
+ maxArgs: 1,
104
+ description: 'Generate random last name'
105
+ },
106
+ 'chance.email': {
107
+ minArgs: 0,
108
+ maxArgs: 1,
109
+ description: 'Generate random email'
110
+ },
111
+ 'chance.company': {
112
+ minArgs: 0,
113
+ maxArgs: 0,
114
+ description: 'Generate random company name'
115
+ },
116
+ 'chance.profession': {
117
+ minArgs: 0,
118
+ maxArgs: 0,
119
+ description: 'Generate random profession'
120
+ },
121
+ 'chance.industry': {
122
+ minArgs: 0,
123
+ maxArgs: 0,
124
+ description: 'Generate random industry'
125
+ },
126
+ 'chance.country': {
127
+ minArgs: 0,
128
+ maxArgs: 1,
129
+ description: 'Generate random country'
130
+ },
131
+ 'chance.city': {
132
+ minArgs: 0,
133
+ maxArgs: 0,
134
+ description: 'Generate random city'
135
+ },
136
+ 'chance.state': {
137
+ minArgs: 0,
138
+ maxArgs: 1,
139
+ description: 'Generate random state'
140
+ },
141
+ 'chance.address': {
142
+ minArgs: 0,
143
+ maxArgs: 1,
144
+ description: 'Generate random address'
145
+ },
146
+ 'chance.phone': {
147
+ minArgs: 0,
148
+ maxArgs: 1,
149
+ description: 'Generate random phone number'
150
+ },
151
+ 'chance.url': {
152
+ minArgs: 0,
153
+ maxArgs: 1,
154
+ description: 'Generate random URL'
155
+ },
156
+ 'chance.domain': {
157
+ minArgs: 0,
158
+ maxArgs: 1,
159
+ description: 'Generate random domain'
160
+ },
161
+ 'chance.ip': {
162
+ minArgs: 0,
163
+ maxArgs: 0,
164
+ description: 'Generate random IP address'
165
+ },
166
+ 'chance.guid': {
167
+ minArgs: 0,
168
+ maxArgs: 1,
169
+ description: 'Generate random GUID'
170
+ },
171
+ 'chance.hash': {
172
+ minArgs: 0,
173
+ maxArgs: 1,
174
+ description: 'Generate random hash'
175
+ },
176
+ 'chance.integer': {
177
+ minArgs: 0,
178
+ maxArgs: 1,
179
+ description: 'Generate random integer with options'
180
+ },
181
+ 'chance.floating': {
182
+ minArgs: 0,
183
+ maxArgs: 1,
184
+ description: 'Generate random float'
185
+ },
186
+ 'chance.bool': {
187
+ minArgs: 0,
188
+ maxArgs: 1,
189
+ description: 'Generate random boolean'
190
+ },
191
+ 'chance.character': {
192
+ minArgs: 0,
193
+ maxArgs: 1,
194
+ description: 'Generate random character'
195
+ },
196
+ 'chance.string': {
197
+ minArgs: 0,
198
+ maxArgs: 1,
199
+ description: 'Generate random string'
200
+ },
201
+ 'chance.pick': {
202
+ minArgs: 1,
203
+ maxArgs: 2,
204
+ description: 'Pick random element from array'
205
+ },
206
+ 'chance.pickone': {
207
+ minArgs: 1,
208
+ maxArgs: 1,
209
+ description: 'Pick one random element from array'
210
+ },
211
+ 'chance.pickset': {
212
+ minArgs: 2,
213
+ maxArgs: 2,
214
+ description: 'Pick set of random elements from array'
215
+ },
216
+ 'chance.cc': {
217
+ minArgs: 0,
218
+ maxArgs: 1,
219
+ description: 'Generate credit card number'
220
+ },
221
+ 'chance.android_id': {
222
+ minArgs: 0,
223
+ maxArgs: 0,
224
+ description: 'Generate Android device ID'
225
+ },
226
+
227
+ // Special function for arrow functions
228
+ arrow: {
229
+ minArgs: 1,
230
+ maxArgs: 1,
231
+ description: 'Raw arrow function with body',
232
+ special: true
233
+ }
234
+ };
235
+
236
+ /**
237
+ * Validate a function call structure
238
+ * @param {Object} funcCall - The function call object with functionName and args
239
+ * @returns {boolean} - Whether the function call is valid
240
+ */
241
+ export function validateFunctionCall(funcCall) {
242
+ if (!funcCall || typeof funcCall !== 'object') {
243
+ return false;
244
+ }
245
+
246
+ const { functionName, args, body } = funcCall;
247
+
248
+ if (!functionName || typeof functionName !== 'string') {
249
+ return false;
250
+ }
251
+
252
+ // Special handling for arrow functions
253
+ if (functionName === 'arrow') {
254
+ return typeof body === 'string' && body.length > 0;
255
+ }
256
+
257
+ const funcDef = FUNCTION_REGISTRY[functionName];
258
+ if (!funcDef) {
259
+ logger.warn({ functionName }, `Unknown function: ${functionName}`);
260
+ return false;
261
+ }
262
+
263
+ // Check args
264
+ if (!Array.isArray(args)) {
265
+ if (funcDef.minArgs === 0 && !args) {
266
+ return true; // No args required and none provided
267
+ }
268
+ return false;
269
+ }
270
+
271
+ if (args.length < funcDef.minArgs || args.length > funcDef.maxArgs) {
272
+ logger.warn({ functionName, expected: `${funcDef.minArgs}-${funcDef.maxArgs}`, actual: args.length }, `Function ${functionName} expects ${funcDef.minArgs}-${funcDef.maxArgs} args, got ${args.length}`);
273
+ return false;
274
+ }
275
+
276
+ return true;
277
+ }
278
+
279
+ /**
280
+ * Get list of all valid function names
281
+ * @returns {string[]} Array of function names
282
+ */
283
+ export function getValidFunctionNames() {
284
+ return Object.keys(FUNCTION_REGISTRY);
285
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * JSON Evaluator for converting JSON function call format to JavaScript
3
+ * Replaces the old regex-based string parsing approach
4
+ */
5
+
6
+ import { validateFunctionCall } from './function-registry.js';
7
+
8
+ /**
9
+ * Evaluate a value that might contain function calls
10
+ * @param {any} value - The value to evaluate (could be object with functionName, array, or primitive)
11
+ * @returns {string} - JavaScript code string
12
+ */
13
+ export function evaluateValue(value) {
14
+ // Handle function call objects
15
+ if (typeof value === 'object' && value !== null && 'functionName' in value) {
16
+ return evaluateFunctionCall(value);
17
+ }
18
+
19
+ // Handle arrays (return as-is)
20
+ if (Array.isArray(value)) {
21
+ return JSON.stringify(value);
22
+ }
23
+
24
+ // Handle nested objects (recursively evaluate)
25
+ if (typeof value === 'object' && value !== null) {
26
+ const entries = Object.entries(value).map(([key, val]) => {
27
+ const evaluatedVal = evaluateValue(val);
28
+ // Use quotes for keys that need them
29
+ const quotedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
30
+ return `${quotedKey}: ${evaluatedVal}`;
31
+ });
32
+ return `{${entries.join(', ')}}`;
33
+ }
34
+
35
+ // Handle primitives
36
+ return JSON.stringify(value);
37
+ }
38
+
39
+ /**
40
+ * Evaluate a function call object to JavaScript code
41
+ * @param {Object} funcCall - Object with functionName and args/body
42
+ * @returns {string} - JavaScript function call string
43
+ */
44
+ export function evaluateFunctionCall(funcCall) {
45
+ if (!validateFunctionCall(funcCall)) {
46
+ throw new Error(`Invalid function call: ${JSON.stringify(funcCall)}`);
47
+ }
48
+
49
+ const { functionName, args, body } = funcCall;
50
+
51
+ // Special handling for arrow functions
52
+ if (functionName === 'arrow') {
53
+ return `() => ${body}`;
54
+ }
55
+
56
+ // Handle chance.* functions
57
+ if (functionName.startsWith('chance.')) {
58
+ const method = functionName.split('.')[1];
59
+ if (!args || args.length === 0) {
60
+ return `chance.${method}.bind(chance)`;
61
+ } else if (args.length === 1 && typeof args[0] === 'object') {
62
+ // For methods that take an options object
63
+ return `() => chance.${method}(${JSON.stringify(args[0])})`;
64
+ } else {
65
+ // For methods with regular arguments
66
+ const argsStr = args.map(arg => evaluateValue(arg)).join(', ');
67
+ return `() => chance.${method}(${argsStr})`;
68
+ }
69
+ }
70
+
71
+ // Handle regular functions
72
+ if (!args || args.length === 0) {
73
+ return `${functionName}()`;
74
+ }
75
+
76
+ // Evaluate each argument
77
+ const evaluatedArgs = args.map(arg => {
78
+ // If arg is an object with functionName, evaluate it as a function call
79
+ if (typeof arg === 'object' && arg !== null && 'functionName' in arg) {
80
+ return evaluateFunctionCall(arg);
81
+ }
82
+ // For primitive values (strings, numbers, booleans), use JSON.stringify
83
+ // but strings need special handling to avoid double-escaping
84
+ if (typeof arg === 'string') {
85
+ return `"${arg}"`;
86
+ }
87
+ // For other primitives and arrays, use JSON.stringify
88
+ return JSON.stringify(arg);
89
+ });
90
+
91
+ return `${functionName}(${evaluatedArgs.join(', ')})`;
92
+ }
93
+
94
+ /**
95
+ * Convert a complete dungeon config from JSON to JavaScript
96
+ * @param {Object} config - The dungeon configuration object
97
+ * @returns {Object} - Configuration with function strings
98
+ */
99
+ export function convertDungeonConfig(config) {
100
+ const converted = {};
101
+
102
+ for (const [key, value] of Object.entries(config)) {
103
+ // Skip certain keys that shouldn't be processed
104
+ if (key === 'seed' || key === 'name' || typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') {
105
+ converted[key] = value;
106
+ continue;
107
+ }
108
+
109
+ // Special handling for groupKeys - preserve as array of arrays
110
+ if (key === 'groupKeys' && Array.isArray(value)) {
111
+ converted[key] = value;
112
+ continue;
113
+ }
114
+
115
+ // Process arrays of objects (like events)
116
+ if (Array.isArray(value)) {
117
+ converted[key] = value.map(item => {
118
+ if (typeof item === 'object' && item !== null) {
119
+ return processConfigObject(item);
120
+ }
121
+ return item;
122
+ });
123
+ }
124
+ // Process objects
125
+ else if (typeof value === 'object' && value !== null) {
126
+ converted[key] = processConfigObject(value);
127
+ }
128
+ else {
129
+ converted[key] = value;
130
+ }
131
+ }
132
+
133
+ return converted;
134
+ }
135
+
136
+ /**
137
+ * Process a configuration object, converting function calls
138
+ * @param {Object} obj - Object to process
139
+ * @returns {Object} - Processed object
140
+ */
141
+ function processConfigObject(obj) {
142
+ const processed = {};
143
+
144
+ for (const [key, value] of Object.entries(obj)) {
145
+ // Check if this is a function call
146
+ if (typeof value === 'object' && value !== null && 'functionName' in value) {
147
+ // Store as string for the JS file generation
148
+ processed[key] = evaluateFunctionCall(value);
149
+ }
150
+ // Process nested arrays
151
+ else if (Array.isArray(value)) {
152
+ processed[key] = value.map(item => {
153
+ if (typeof item === 'object' && item !== null && 'functionName' in item) {
154
+ return evaluateFunctionCall(item);
155
+ }
156
+ if (typeof item === 'object' && item !== null) {
157
+ return processConfigObject(item);
158
+ }
159
+ return item;
160
+ });
161
+ }
162
+ // Process nested objects
163
+ else if (typeof value === 'object' && value !== null) {
164
+ processed[key] = processConfigObject(value);
165
+ }
166
+ else {
167
+ processed[key] = value;
168
+ }
169
+ }
170
+
171
+ return processed;
172
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Structured logger using Pino
3
+ *
4
+ * Pretty-printed in development, JSON in production for Cloud Run
5
+ */
6
+
7
+ import pino from 'pino';
8
+
9
+ const isDev = process.env.NODE_ENV !== 'production';
10
+ const isTest = process.env.NODE_ENV === 'test';
11
+
12
+ // Create the logger with appropriate transport
13
+ // In production, use 'message' instead of 'msg' for GCP Cloud Logging compatibility
14
+ const logger = pino({
15
+ level: process.env.LOG_LEVEL || (isTest ? 'silent' : isDev ? 'warn' : 'info'),
16
+ // Use 'message' instead of 'msg' for GCP Cloud Logging
17
+ messageKey: isDev ? 'msg' : 'message',
18
+ transport: isDev
19
+ ? {
20
+ target: 'pino-pretty',
21
+ options: {
22
+ colorize: true,
23
+ translateTime: 'HH:MM:ss.l',
24
+ ignore: 'pid,hostname',
25
+ messageFormat: '{msg}',
26
+ errorLikeObjectKeys: ['err', 'error']
27
+ }
28
+ }
29
+ : undefined // JSON output in production for Cloud Run
30
+ });
31
+
32
+ // Add child loggers for different components
33
+ export const serverLogger = logger.child({ component: 'server' });
34
+ export const aiLogger = logger.child({ component: 'ai' });
35
+ export const dataLogger = logger.child({ component: 'data' });
36
+ export const importLogger = logger.child({ component: 'import' });
37
+
38
+ export default logger;