@egdesk/next-api-plugin 1.0.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.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Generate helper functions for EGDesk database access in Next.js
3
+ *
4
+ * Creates egdesk-helpers.ts with type-safe helper functions.
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+
10
+ /**
11
+ * Generate egdesk-helpers.ts file
12
+ */
13
+ export function generateHelpers(projectPath: string): void {
14
+ const helperPath = path.join(projectPath, 'egdesk-helpers.ts');
15
+
16
+ const helperContent = `/**
17
+ * EGDesk User Data Helper Functions for Next.js
18
+ *
19
+ * Type-safe helpers for accessing EGDesk user data.
20
+ * Works in both client and server components.
21
+ *
22
+ * Generated by @egdesk/next-api-plugin
23
+ */
24
+
25
+ /**
26
+ * Call EGDesk user-data MCP tool
27
+ *
28
+ * Uses a proxy endpoint to work in both local and tunneled environments.
29
+ * The Next.js middleware intercepts these requests and forwards them to localhost:8080.
30
+ */
31
+ export async function callUserDataTool(
32
+ toolName: string,
33
+ args: Record<string, any> = {}
34
+ ): Promise<any> {
35
+ const headers: Record<string, string> = {
36
+ 'Content-Type': 'application/json'
37
+ };
38
+
39
+ // Use proxy endpoint - works in both local and tunneled environments
40
+ // Absolute URL with leading slash to ensure correct resolution from any route
41
+ const response = await fetch('/__user_data_proxy', {
42
+ method: 'POST',
43
+ headers,
44
+ body: JSON.stringify({ tool: toolName, arguments: args })
45
+ });
46
+
47
+ if (!response.ok) {
48
+ throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
49
+ }
50
+
51
+ const result = await response.json();
52
+
53
+ if (!result.success) {
54
+ throw new Error(result.error || 'Tool call failed');
55
+ }
56
+
57
+ // Parse MCP response format
58
+ const content = result.result?.content?.[0]?.text;
59
+ return content ? JSON.parse(content) : null;
60
+ }
61
+
62
+ /**
63
+ * Query table data
64
+ */
65
+ export async function queryTable(
66
+ tableName: string,
67
+ options: {
68
+ filters?: Record<string, string>;
69
+ limit?: number;
70
+ offset?: number;
71
+ orderBy?: string;
72
+ orderDirection?: 'ASC' | 'DESC';
73
+ } = {}
74
+ ) {
75
+ return callUserDataTool('user_data_query', {
76
+ tableName,
77
+ ...options
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Search table
83
+ */
84
+ export async function searchTable(
85
+ tableName: string,
86
+ searchQuery: string,
87
+ limit: number = 50
88
+ ) {
89
+ return callUserDataTool('user_data_search', {
90
+ tableName,
91
+ searchQuery,
92
+ limit
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Aggregate data
98
+ */
99
+ export async function aggregateTable(
100
+ tableName: string,
101
+ column: string,
102
+ aggregateFunction: 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'COUNT',
103
+ options: {
104
+ filters?: Record<string, string>;
105
+ groupBy?: string;
106
+ } = {}
107
+ ) {
108
+ return callUserDataTool('user_data_aggregate', {
109
+ tableName,
110
+ column,
111
+ function: aggregateFunction,
112
+ ...options
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Execute raw SQL query
118
+ */
119
+ export async function executeSQL(query: string) {
120
+ return callUserDataTool('user_data_sql_query', { query });
121
+ }
122
+
123
+ /**
124
+ * List all available tables
125
+ */
126
+ export async function listTables() {
127
+ return callUserDataTool('user_data_list_tables', {});
128
+ }
129
+
130
+ /**
131
+ * Get table schema
132
+ */
133
+ export async function getTableSchema(tableName: string) {
134
+ return callUserDataTool('user_data_get_schema', { tableName });
135
+ }
136
+ `;
137
+
138
+ fs.writeFileSync(helperPath, helperContent, 'utf-8');
139
+ console.log(`✅ Generated ${helperPath}`);
140
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Generate Next.js middleware for EGDesk database proxy
3
+ *
4
+ * Creates middleware.ts that intercepts __user_data_proxy requests
5
+ * and forwards them to the EGDesk MCP server.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+
11
+ /**
12
+ * Detect if project uses src/ directory structure
13
+ */
14
+ function usesSrcDirectory(projectPath: string): boolean {
15
+ const srcPath = path.join(projectPath, 'src');
16
+ const srcAppPath = path.join(projectPath, 'src', 'app');
17
+ return fs.existsSync(srcPath) && fs.existsSync(srcAppPath);
18
+ }
19
+
20
+ /**
21
+ * Generate middleware.ts file in the correct location (root or src/)
22
+ */
23
+ export function generateMiddleware(projectPath: string): void {
24
+ // Detect if project uses src/ directory
25
+ const hasSrc = usesSrcDirectory(projectPath);
26
+ const targetDir = hasSrc ? path.join(projectPath, 'src') : projectPath;
27
+ const middlewarePath = path.join(targetDir, 'middleware.ts');
28
+
29
+ console.log(`📁 Detected ${hasSrc ? 'src/' : 'root'} directory structure`);
30
+ console.log(`📝 Generating middleware at: ${middlewarePath}`);
31
+
32
+ // Check if middleware.ts already exists
33
+ if (fs.existsSync(middlewarePath)) {
34
+ console.log('⚠️ middleware.ts already exists - backing up to middleware.backup.ts');
35
+ const backupPath = path.join(targetDir, 'middleware.backup.ts');
36
+ fs.copyFileSync(middlewarePath, backupPath);
37
+ }
38
+
39
+ const middlewareContent = `import { NextResponse } from 'next/server';
40
+ import type { NextRequest } from 'next/server';
41
+
42
+ /**
43
+ * EGDesk Database Proxy Middleware
44
+ *
45
+ * Intercepts __user_data_proxy requests and forwards them to the EGDesk MCP server.
46
+ * This allows CORS-free database access in both local and tunneled environments.
47
+ *
48
+ * Generated by @egdesk/next-api-plugin
49
+ */
50
+ export async function middleware(request: NextRequest) {
51
+ const { pathname } = request.nextUrl;
52
+
53
+ // Intercept __user_data_proxy requests
54
+ if (pathname.includes('__user_data_proxy')) {
55
+ try {
56
+ const body = await request.text();
57
+
58
+ // Read API key and URL from environment
59
+ const apiKey = process.env.NEXT_PUBLIC_EGDESK_API_KEY;
60
+ const apiUrl = process.env.NEXT_PUBLIC_EGDESK_API_URL || 'http://localhost:8080';
61
+
62
+ const headers: HeadersInit = {
63
+ 'Content-Type': 'application/json',
64
+ };
65
+
66
+ if (apiKey) {
67
+ headers['X-Api-Key'] = apiKey;
68
+ }
69
+
70
+ // Forward to EGDesk MCP server
71
+ const response = await fetch(\`\${apiUrl}/user-data/tools/call\`, {
72
+ method: 'POST',
73
+ headers,
74
+ body,
75
+ });
76
+
77
+ const result = await response.json();
78
+
79
+ return NextResponse.json(result, { status: response.status });
80
+ } catch (error: any) {
81
+ return NextResponse.json(
82
+ { error: 'Proxy error', message: error.message },
83
+ { status: 500 }
84
+ );
85
+ }
86
+ }
87
+
88
+ // Continue to next middleware or route
89
+ return NextResponse.next();
90
+ }
91
+
92
+ export const config = {
93
+ matcher: [
94
+ /*
95
+ * Match all request paths except for the ones starting with:
96
+ * - api (API routes)
97
+ * - _next/static (static files)
98
+ * - _next/image (image optimization files)
99
+ * - favicon.ico (favicon file)
100
+ */
101
+ '/((?!api|_next/static|_next/image|favicon.ico).*)',
102
+ ],
103
+ };
104
+ `;
105
+
106
+ fs.writeFileSync(middlewarePath, middlewareContent, 'utf-8');
107
+ console.log(`✅ Generated ${middlewarePath}`);
108
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Generate Next.js 16+ proxy for EGDesk database proxy
3
+ *
4
+ * Creates proxy.ts that intercepts __user_data_proxy requests
5
+ * and forwards them to the EGDesk MCP server.
6
+ *
7
+ * This is the Next.js 16+ recommended approach (replaces middleware.ts)
8
+ */
9
+
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+
13
+ /**
14
+ * Detect if project uses src/ directory structure
15
+ */
16
+ function usesSrcDirectory(projectPath: string): boolean {
17
+ const srcPath = path.join(projectPath, 'src');
18
+ const srcAppPath = path.join(projectPath, 'src', 'app');
19
+ return fs.existsSync(srcPath) && fs.existsSync(srcAppPath);
20
+ }
21
+
22
+ /**
23
+ * Generate proxy.ts file in the correct location (root or src/)
24
+ */
25
+ export function generateProxy(projectPath: string): void {
26
+ // Detect if project uses src/ directory
27
+ const hasSrc = usesSrcDirectory(projectPath);
28
+ const targetDir = hasSrc ? path.join(projectPath, 'src') : projectPath;
29
+ const proxyPath = path.join(targetDir, 'proxy.ts');
30
+
31
+ console.log(`📁 Detected ${hasSrc ? 'src/' : 'root'} directory structure`);
32
+ console.log(`📝 Generating proxy at: ${proxyPath}`);
33
+
34
+ // Check if proxy.ts already exists
35
+ if (fs.existsSync(proxyPath)) {
36
+ console.log('⚠️ proxy.ts already exists - backing up to proxy.backup.ts');
37
+ const backupPath = path.join(targetDir, 'proxy.backup.ts');
38
+ fs.copyFileSync(proxyPath, backupPath);
39
+ }
40
+
41
+ const proxyContent = `import { NextResponse } from 'next/server';
42
+ import type { NextRequest } from 'next/server';
43
+
44
+ /**
45
+ * EGDesk Database Proxy (Next.js 16+)
46
+ *
47
+ * Intercepts __user_data_proxy requests and forwards them to the EGDesk MCP server.
48
+ * This allows CORS-free database access in both local and tunneled environments.
49
+ *
50
+ * Generated by @egdesk/next-api-plugin
51
+ */
52
+ export async function proxy(request: NextRequest) {
53
+ const { pathname } = request.nextUrl;
54
+
55
+ // Intercept __user_data_proxy requests
56
+ if (pathname.includes('__user_data_proxy')) {
57
+ try {
58
+ const body = await request.text();
59
+
60
+ // Read API key and URL from environment
61
+ const apiKey = process.env.NEXT_PUBLIC_EGDESK_API_KEY;
62
+ const apiUrl = process.env.NEXT_PUBLIC_EGDESK_API_URL || 'http://localhost:8080';
63
+
64
+ const headers: HeadersInit = {
65
+ 'Content-Type': 'application/json',
66
+ };
67
+
68
+ if (apiKey) {
69
+ headers['X-Api-Key'] = apiKey;
70
+ }
71
+
72
+ // Forward to EGDesk MCP server
73
+ const response = await fetch(\`\${apiUrl}/user-data/tools/call\`, {
74
+ method: 'POST',
75
+ headers,
76
+ body,
77
+ });
78
+
79
+ const result = await response.json();
80
+
81
+ return NextResponse.json(result, { status: response.status });
82
+ } catch (error: any) {
83
+ return NextResponse.json(
84
+ { error: 'Proxy error', message: error.message },
85
+ { status: 500 }
86
+ );
87
+ }
88
+ }
89
+
90
+ // Continue to next proxy or route
91
+ return NextResponse.next();
92
+ }
93
+
94
+ export const config = {
95
+ matcher: [
96
+ /*
97
+ * Match all paths except static assets
98
+ * The proxy function itself filters for __user_data_proxy
99
+ */
100
+ '/:path*',
101
+ ],
102
+ };
103
+ `;
104
+
105
+ fs.writeFileSync(proxyPath, proxyContent, 'utf-8');
106
+ console.log(`✅ Generated ${proxyPath}`);
107
+ }
package/src/index.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @egdesk/next-api-plugin
3
+ *
4
+ * Next.js plugin for EGDesk database proxy integration.
5
+ * Provides middleware-based database access for Next.js applications.
6
+ */
7
+
8
+ import {
9
+ discoverTables,
10
+ updateEnvLocal,
11
+ generateConfigFile,
12
+ type UserDataConfig,
13
+ type UserDataTable
14
+ } from './setup-userdata';
15
+ import { generateMiddleware } from './generate-middleware';
16
+ import { generateProxy } from './generate-proxy';
17
+ import { generateHelpers } from './generate-helpers';
18
+
19
+ export interface SetupOptions {
20
+ egdeskUrl?: string;
21
+ apiKey?: string;
22
+ useProxy?: boolean; // Use proxy.ts (Next.js 16+) instead of middleware.ts
23
+ }
24
+
25
+ /**
26
+ * Main setup function for Next.js projects
27
+ *
28
+ * Discovers tables and generates all necessary files:
29
+ * - proxy.ts or middleware.ts (proxy interceptor)
30
+ * - egdesk.config.ts (table definitions)
31
+ * - egdesk-helpers.ts (helper functions)
32
+ * - .env.local (environment variables)
33
+ */
34
+ export async function setupNextApiPlugin(
35
+ projectPath: string,
36
+ options: SetupOptions = {}
37
+ ): Promise<void> {
38
+ const { egdeskUrl = 'http://localhost:8080', apiKey, useProxy = true } = options;
39
+
40
+ console.log('🔍 Discovering EGDesk user-data tables...');
41
+
42
+ try {
43
+ const tables = await discoverTables(egdeskUrl, apiKey);
44
+
45
+ if (tables.length === 0) {
46
+ console.warn('⚠️ No tables found. Import data in EGDesk first.');
47
+ console.log('');
48
+ console.log('Generating files anyway with empty table list...');
49
+ } else {
50
+ console.log(`✅ Found ${tables.length} table(s):`);
51
+ tables.forEach((table, index) => {
52
+ console.log(` ${index + 1}. ${table.displayName} (${table.rowCount} rows)`);
53
+ });
54
+ }
55
+
56
+ const config: UserDataConfig = {
57
+ apiKey: apiKey || null,
58
+ baseUrl: egdeskUrl,
59
+ tables,
60
+ generatedAt: new Date().toISOString()
61
+ };
62
+
63
+ // Generate all configuration files
64
+ console.log('');
65
+ console.log('📝 Generating configuration files...');
66
+
67
+ // Use proxy.ts (Next.js 16+) or middleware.ts (legacy)
68
+ if (useProxy) {
69
+ console.log('🔄 Using proxy.ts (Next.js 16+ recommended)');
70
+ generateProxy(projectPath);
71
+ } else {
72
+ console.log('🔄 Using middleware.ts (legacy)');
73
+ generateMiddleware(projectPath);
74
+ }
75
+
76
+ generateConfigFile(projectPath, config);
77
+ generateHelpers(projectPath);
78
+ updateEnvLocal(projectPath, config);
79
+
80
+ const proxyFile = useProxy ? 'proxy.ts' : 'middleware.ts';
81
+
82
+ console.log('');
83
+ console.log('✅ Setup complete! Files generated:');
84
+ console.log(` - ${proxyFile} (database proxy)`);
85
+ console.log(' - egdesk.config.ts (type-safe config)');
86
+ console.log(' - egdesk-helpers.ts (helper functions)');
87
+ console.log(' - .env.local (environment variables)');
88
+ console.log('');
89
+ console.log('📝 Next steps:');
90
+ console.log(' 1. Add .env.local to your .gitignore');
91
+ console.log(' 2. Restart your Next.js dev server');
92
+ console.log(' 3. Import and use helpers in your components:');
93
+ console.log(' import { queryTable } from "./egdesk-helpers"');
94
+ console.log(' import { TABLES } from "./egdesk.config"');
95
+ console.log('');
96
+ console.log('Example usage in a component:');
97
+ console.log(' const data = await queryTable(TABLES.table1.name, { limit: 10 });');
98
+ } catch (error) {
99
+ console.error('❌ Setup failed:', error);
100
+ throw error;
101
+ }
102
+ }
103
+
104
+ // Export types and utilities
105
+ export type { UserDataTable, UserDataConfig } from './setup-userdata';
106
+ export { discoverTables } from './setup-userdata';
107
+ export { generateMiddleware } from './generate-middleware';
108
+ export { generateProxy } from './generate-proxy';
109
+ export { generateHelpers } from './generate-helpers';
@@ -0,0 +1,238 @@
1
+ /**
2
+ * User Data Setup Utility for Next.js
3
+ *
4
+ * Automatically discovers EGDesk user-data tables and generates configuration
5
+ * for Next.js projects to access them.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+
11
+ export interface UserDataTable {
12
+ id: string;
13
+ tableName: string;
14
+ displayName: string;
15
+ description?: string;
16
+ rowCount: number;
17
+ columnCount: number;
18
+ columns: string[];
19
+ }
20
+
21
+ export interface UserDataConfig {
22
+ apiKey: string | null;
23
+ baseUrl: string;
24
+ tables: UserDataTable[];
25
+ generatedAt: string;
26
+ }
27
+
28
+ /**
29
+ * Fetch available tables from EGDesk
30
+ */
31
+ export async function discoverTables(
32
+ egdeskUrl: string = 'http://localhost:8080',
33
+ apiKey?: string
34
+ ): Promise<UserDataTable[]> {
35
+ try {
36
+ const headers: Record<string, string> = {
37
+ 'Content-Type': 'application/json'
38
+ };
39
+
40
+ if (apiKey) {
41
+ headers['X-Api-Key'] = apiKey;
42
+ }
43
+
44
+ // List all tables
45
+ const listResponse = await fetch(`${egdeskUrl}/user-data/tools/call`, {
46
+ method: 'POST',
47
+ headers,
48
+ body: JSON.stringify({
49
+ tool: 'user_data_list_tables',
50
+ arguments: {}
51
+ })
52
+ });
53
+
54
+ if (!listResponse.ok) {
55
+ throw new Error(`Failed to list tables: ${listResponse.statusText}`);
56
+ }
57
+
58
+ const listResult = await listResponse.json();
59
+
60
+ if (!listResult.success) {
61
+ throw new Error(listResult.error || 'Failed to list tables');
62
+ }
63
+
64
+ // Parse MCP response
65
+ const content = listResult.result?.content?.[0]?.text;
66
+ const data = content ? JSON.parse(content) : null;
67
+
68
+ if (!data || !data.tables) {
69
+ return [];
70
+ }
71
+
72
+ // Fetch schema for each table to get column names
73
+ const tablesWithColumns: UserDataTable[] = [];
74
+
75
+ for (const table of data.tables) {
76
+ try {
77
+ const schemaResponse = await fetch(`${egdeskUrl}/user-data/tools/call`, {
78
+ method: 'POST',
79
+ headers,
80
+ body: JSON.stringify({
81
+ tool: 'user_data_get_schema',
82
+ arguments: { tableName: table.tableName }
83
+ })
84
+ });
85
+
86
+ if (schemaResponse.ok) {
87
+ const schemaResult = await schemaResponse.json();
88
+ const schemaContent = schemaResult.result?.content?.[0]?.text;
89
+ const schemaData = schemaContent ? JSON.parse(schemaContent) : null;
90
+
91
+ tablesWithColumns.push({
92
+ id: table.id,
93
+ tableName: table.tableName,
94
+ displayName: table.displayName,
95
+ description: table.description,
96
+ rowCount: table.rowCount,
97
+ columnCount: table.columnCount,
98
+ columns: schemaData?.schema?.map((col: any) => col.name) || []
99
+ });
100
+ }
101
+ } catch (error) {
102
+ console.warn(`Failed to fetch schema for ${table.tableName}:`, error);
103
+ // Add table without column info
104
+ tablesWithColumns.push({
105
+ ...table,
106
+ columns: []
107
+ });
108
+ }
109
+ }
110
+
111
+ return tablesWithColumns;
112
+ } catch (error) {
113
+ console.error('Failed to discover tables:', error);
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Read existing .env.local file or return empty object
120
+ */
121
+ export function readEnvLocal(projectPath: string): Record<string, string> {
122
+ const envPath = path.join(projectPath, '.env.local');
123
+
124
+ if (!fs.existsSync(envPath)) {
125
+ return {};
126
+ }
127
+
128
+ const envContent = fs.readFileSync(envPath, 'utf-8');
129
+ const envVars: Record<string, string> = {};
130
+
131
+ for (const line of envContent.split('\n')) {
132
+ const trimmed = line.trim();
133
+ if (trimmed && !trimmed.startsWith('#')) {
134
+ const [key, ...valueParts] = trimmed.split('=');
135
+ if (key && valueParts.length > 0) {
136
+ envVars[key.trim()] = valueParts.join('=').trim();
137
+ }
138
+ }
139
+ }
140
+
141
+ return envVars;
142
+ }
143
+
144
+ /**
145
+ * Update .env.local file with Next.js environment variables
146
+ */
147
+ export function updateEnvLocal(
148
+ projectPath: string,
149
+ config: UserDataConfig
150
+ ): void {
151
+ const envPath = path.join(projectPath, '.env.local');
152
+ const existingVars = readEnvLocal(projectPath);
153
+
154
+ // Merge with existing vars, prioritizing new EGDesk values
155
+ const newVars = {
156
+ ...existingVars,
157
+ 'NEXT_PUBLIC_EGDESK_API_URL': config.baseUrl,
158
+ ...(config.apiKey && { 'NEXT_PUBLIC_EGDESK_API_KEY': config.apiKey })
159
+ };
160
+
161
+ const envContent = [
162
+ '# EGDesk User Data Configuration',
163
+ `# Generated at: ${config.generatedAt}`,
164
+ '',
165
+ ...Object.entries(newVars).map(([key, value]) => `${key}=${value}`),
166
+ '',
167
+ '# Available Tables',
168
+ `# Total tables: ${config.tables.length}`,
169
+ ...config.tables.map((table, index) =>
170
+ `# ${index + 1}. ${table.displayName} (${table.tableName}) - ${table.rowCount} rows, ${table.columnCount} columns`
171
+ ),
172
+ ''
173
+ ].join('\n');
174
+
175
+ fs.writeFileSync(envPath, envContent, 'utf-8');
176
+ console.log(`✅ Updated ${envPath}`);
177
+ }
178
+
179
+ /**
180
+ * Generate TypeScript config file with table definitions
181
+ */
182
+ export function generateConfigFile(
183
+ projectPath: string,
184
+ config: UserDataConfig
185
+ ): void {
186
+ const configPath = path.join(projectPath, 'egdesk.config.ts');
187
+
188
+ const configContent = `/**
189
+ * EGDesk User Data Configuration
190
+ * Generated at: ${config.generatedAt}
191
+ *
192
+ * This file contains type-safe definitions for your EGDesk tables.
193
+ */
194
+
195
+ export const EGDESK_CONFIG = {
196
+ apiUrl: '${config.baseUrl}',
197
+ apiKey: ${config.apiKey ? `'${config.apiKey}'` : 'undefined'},
198
+ } as const;
199
+
200
+ export interface TableDefinition {
201
+ name: string;
202
+ displayName: string;
203
+ description?: string;
204
+ rowCount: number;
205
+ columnCount: number;
206
+ columns: string[];
207
+ }
208
+
209
+ export const TABLES = {
210
+ ${config.tables.map((table, index) => ` table${index + 1}: {
211
+ name: '${table.tableName}',
212
+ displayName: '${table.displayName}',
213
+ description: ${table.description ? `'${table.description}'` : 'undefined'},
214
+ rowCount: ${table.rowCount},
215
+ columnCount: ${table.columnCount},
216
+ columns: [${table.columns.map(col => `'${col}'`).join(', ')}]
217
+ } as TableDefinition`).join(',\n')}
218
+ } as const;
219
+
220
+ ${config.tables.length > 0 ? `
221
+ // Main table (first table by default)
222
+ export const MAIN_TABLE = TABLES.table1;
223
+ ` : ''}
224
+
225
+ // Helper to get table by name
226
+ export function getTableByName(tableName: string): TableDefinition | undefined {
227
+ return Object.values(TABLES).find(t => t.name === tableName);
228
+ }
229
+
230
+ // Export table names for easy access
231
+ export const TABLE_NAMES = {
232
+ ${config.tables.map((table, index) => ` table${index + 1}: '${table.tableName}'`).join(',\n')}
233
+ } as const;
234
+ `;
235
+
236
+ fs.writeFileSync(configPath, configContent, 'utf-8');
237
+ console.log(`✅ Generated ${configPath}`);
238
+ }