@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.
- package/README.md +176 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +60 -0
- package/dist/generate-helpers.d.ts +9 -0
- package/dist/generate-helpers.js +172 -0
- package/dist/generate-middleware.d.ts +10 -0
- package/dist/generate-middleware.js +137 -0
- package/dist/generate-proxy.d.ts +12 -0
- package/dist/generate-proxy.js +136 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +91 -0
- package/dist/setup-userdata.d.ts +37 -0
- package/dist/setup-userdata.js +225 -0
- package/package.json +34 -0
- package/src/cli.ts +63 -0
- package/src/generate-helpers.ts +140 -0
- package/src/generate-middleware.ts +108 -0
- package/src/generate-proxy.ts +107 -0
- package/src/index.ts +109 -0
- package/src/setup-userdata.ts +238 -0
- package/tsconfig.json +18 -0
|
@@ -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
|
+
}
|