@egdesk/next-api-plugin 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generate API wrapper for handling basePath in client-side fetch calls
3
+ *
4
+ * Creates src/lib/api.ts that automatically prepends NEXT_PUBLIC_EGDESK_BASE_PATH
5
+ * to relative URLs, solving the Next.js basePath limitation with client-side fetch.
6
+ */
7
+ /**
8
+ * Generate src/lib/api.ts file for basePath-aware fetch wrapper
9
+ */
10
+ export declare function generateApiWrapper(projectPath: string): void;
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /**
3
+ * Generate API wrapper for handling basePath in client-side fetch calls
4
+ *
5
+ * Creates src/lib/api.ts that automatically prepends NEXT_PUBLIC_EGDESK_BASE_PATH
6
+ * to relative URLs, solving the Next.js basePath limitation with client-side fetch.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.generateApiWrapper = generateApiWrapper;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ /**
47
+ * Detect if project uses src/ directory structure
48
+ */
49
+ function usesSrcDirectory(projectPath) {
50
+ const srcPath = path.join(projectPath, 'src');
51
+ const srcAppPath = path.join(projectPath, 'src', 'app');
52
+ return fs.existsSync(srcPath) && fs.existsSync(srcAppPath);
53
+ }
54
+ /**
55
+ * Ensure lib directory exists
56
+ */
57
+ function ensureLibDirectory(projectPath) {
58
+ const hasSrc = usesSrcDirectory(projectPath);
59
+ const libDir = hasSrc
60
+ ? path.join(projectPath, 'src', 'lib')
61
+ : path.join(projectPath, 'lib');
62
+ if (!fs.existsSync(libDir)) {
63
+ fs.mkdirSync(libDir, { recursive: true });
64
+ console.log(`📁 Created directory: ${libDir}`);
65
+ }
66
+ return libDir;
67
+ }
68
+ /**
69
+ * Generate src/lib/api.ts file for basePath-aware fetch wrapper
70
+ */
71
+ function generateApiWrapper(projectPath) {
72
+ const libDir = ensureLibDirectory(projectPath);
73
+ const apiPath = path.join(libDir, 'api.ts');
74
+ console.log(`📝 Generating API wrapper at: ${apiPath}`);
75
+ // Check if api.ts already exists
76
+ if (fs.existsSync(apiPath)) {
77
+ console.log('⚠️ api.ts already exists - backing up to api.backup.ts');
78
+ const backupPath = path.join(libDir, 'api.backup.ts');
79
+ fs.copyFileSync(apiPath, backupPath);
80
+ }
81
+ const apiContent = `/**
82
+ * API Fetch Wrapper for EGDesk Tunneling
83
+ *
84
+ * This wrapper automatically prepends NEXT_PUBLIC_EGDESK_BASE_PATH to relative URLs.
85
+ * This is necessary because Next.js basePath does NOT automatically apply to
86
+ * client-side fetch() calls - it's a known limitation.
87
+ *
88
+ * Generated by @egdesk/next-api-plugin
89
+ */
90
+
91
+ const basePath = process.env.NEXT_PUBLIC_EGDESK_BASE_PATH || '';
92
+
93
+ /**
94
+ * Fetch wrapper that handles basePath for tunneled environments
95
+ *
96
+ * @example
97
+ * // ❌ Wrong - will return 404 in tunneled environment
98
+ * fetch('/api/transactions')
99
+ *
100
+ * @example
101
+ * // ✅ Correct - automatically prepends basePath
102
+ * apiFetch('/api/transactions')
103
+ *
104
+ * @param path - The API path (e.g., '/api/transactions')
105
+ * @param options - Standard fetch options
106
+ */
107
+ export async function apiFetch(path: string, options?: RequestInit): Promise<Response> {
108
+ // Automatically prepend basePath to relative URLs
109
+ const url = path.startsWith('/') && !path.startsWith('//')
110
+ ? \`\${basePath}\${path}\`
111
+ : path;
112
+
113
+ return fetch(url, options);
114
+ }
115
+ `;
116
+ fs.writeFileSync(apiPath, apiContent.replace(/\r?\n/g, os.EOL), 'utf-8');
117
+ console.log(`✅ Generated ${apiPath}`);
118
+ }
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.generateHelpers = generateHelpers;
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
+ const os = __importStar(require("os"));
44
45
  /**
45
46
  * Generate egdesk-helpers.ts file
46
47
  */
@@ -55,27 +56,52 @@ function generateHelpers(projectPath) {
55
56
  * Generated by @egdesk/next-api-plugin
56
57
  */
57
58
 
59
+ import { EGDESK_CONFIG } from './egdesk.config';
60
+
58
61
  /**
59
62
  * Call EGDesk user-data MCP tool
60
63
  *
61
- * Uses a proxy endpoint to work in both local and tunneled environments.
62
- * The Next.js middleware intercepts these requests and forwards them to localhost:8080.
64
+ * - Server (API routes): Calls Egdesk API directly using EGDESK_CONFIG.apiUrl and
65
+ * EGDESK_CONFIG.apiKey (from env NEXT_PUBLIC_EGDESK_API_URL / NEXT_PUBLIC_EGDESK_API_KEY)
66
+ * so relative URLs and tunnel base path are not an issue.
67
+ * - Client (browser): Uses /__user_data_proxy so CORS and tunnel base path still work.
63
68
  */
64
69
  export async function callUserDataTool(
65
70
  toolName: string,
66
71
  args: Record<string, any> = {}
67
72
  ): Promise<any> {
73
+ const body = JSON.stringify({ tool: toolName, arguments: args });
68
74
  const headers: Record<string, string> = {
69
75
  'Content-Type': 'application/json'
70
76
  };
71
77
 
72
- // Use proxy endpoint - works in both local and tunneled environments
73
- // Absolute URL with leading slash to ensure correct resolution from any route
74
- const response = await fetch('/__user_data_proxy', {
75
- method: 'POST',
76
- headers,
77
- body: JSON.stringify({ tool: toolName, arguments: args })
78
- });
78
+ const isServer = typeof window === 'undefined';
79
+
80
+ let response: Response;
81
+ if (isServer) {
82
+ // API routes: call Egdesk directly (relative URL is invalid in Node)
83
+ const apiUrl =
84
+ (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_URL) ||
85
+ EGDESK_CONFIG.apiUrl;
86
+ const apiKey =
87
+ (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_KEY) ||
88
+ EGDESK_CONFIG.apiKey;
89
+ if (apiKey) {
90
+ headers['X-Api-Key'] = apiKey;
91
+ }
92
+ response = await fetch(\`\${apiUrl}/user-data/tools/call\`, {
93
+ method: 'POST',
94
+ headers,
95
+ body
96
+ });
97
+ } else {
98
+ // Browser: use proxy for CORS and tunnel base path
99
+ response = await fetch('/__user_data_proxy', {
100
+ method: 'POST',
101
+ headers,
102
+ body
103
+ });
104
+ }
79
105
 
80
106
  if (!response.ok) {
81
107
  throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
@@ -147,7 +173,7 @@ export async function aggregateTable(
147
173
  }
148
174
 
149
175
  /**
150
- * Execute raw SQL query
176
+ * Execute raw SQL query (read-only, SELECT only)
151
177
  */
152
178
  export async function executeSQL(query: string) {
153
179
  return callUserDataTool('user_data_sql_query', { query });
@@ -166,7 +192,101 @@ export async function listTables() {
166
192
  export async function getTableSchema(tableName: string) {
167
193
  return callUserDataTool('user_data_get_schema', { tableName });
168
194
  }
195
+
196
+ /**
197
+ * Create a new table
198
+ */
199
+ export async function createTable(
200
+ displayName: string,
201
+ schema: Array<{
202
+ name: string;
203
+ type: 'TEXT' | 'INTEGER' | 'REAL' | 'DATE';
204
+ notNull?: boolean;
205
+ defaultValue?: any;
206
+ }>,
207
+ options?: {
208
+ description?: string;
209
+ tableName?: string;
210
+ uniqueKeyColumns?: string[];
211
+ duplicateAction?: 'skip' | 'update' | 'allow' | 'replace-date-range';
212
+ }
213
+ ) {
214
+ return callUserDataTool('user_data_create_table', {
215
+ displayName,
216
+ schema,
217
+ ...options
218
+ });
219
+ }
220
+
221
+ /**
222
+ * Insert rows into a table
223
+ */
224
+ export async function insertRows(
225
+ tableName: string,
226
+ rows: Array<Record<string, any>>
227
+ ) {
228
+ return callUserDataTool('user_data_insert_rows', {
229
+ tableName,
230
+ rows
231
+ });
232
+ }
233
+
234
+ /**
235
+ * Update rows in a table
236
+ */
237
+ export async function updateRows(
238
+ tableName: string,
239
+ updates: Record<string, any>,
240
+ options: {
241
+ ids?: number[];
242
+ filters?: Record<string, string>;
243
+ }
244
+ ) {
245
+ return callUserDataTool('user_data_update_rows', {
246
+ tableName,
247
+ updates,
248
+ ...options
249
+ });
250
+ }
251
+
252
+ /**
253
+ * Delete rows from a table
254
+ */
255
+ export async function deleteRows(
256
+ tableName: string,
257
+ options: {
258
+ ids?: number[];
259
+ filters?: Record<string, string>;
260
+ }
261
+ ) {
262
+ return callUserDataTool('user_data_delete_rows', {
263
+ tableName,
264
+ ...options
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Delete a table
270
+ */
271
+ export async function deleteTable(tableName: string) {
272
+ return callUserDataTool('user_data_delete_table', { tableName });
273
+ }
274
+
275
+ /**
276
+ * Rename a table
277
+ */
278
+ export async function renameTable(
279
+ tableName: string,
280
+ newTableName: string,
281
+ newDisplayName?: string
282
+ ) {
283
+ return callUserDataTool('user_data_rename_table', {
284
+ tableName,
285
+ newTableName,
286
+ newDisplayName
287
+ });
288
+ }
169
289
  `;
170
- fs.writeFileSync(helperPath, helperContent, 'utf-8');
290
+ fs.writeFileSync(helperPath, helperContent.replace(/\r?\n/g, os.EOL), 'utf-8');
171
291
  console.log(`✅ Generated ${helperPath}`);
172
292
  }
@@ -42,6 +42,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.generateMiddleware = generateMiddleware;
43
43
  const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
45
46
  /**
46
47
  * Detect if project uses src/ directory structure
47
48
  */
@@ -132,6 +133,6 @@ export const config = {
132
133
  ],
133
134
  };
134
135
  `;
135
- fs.writeFileSync(middlewarePath, middlewareContent, 'utf-8');
136
+ fs.writeFileSync(middlewarePath, middlewareContent.replace(/\r?\n/g, os.EOL), 'utf-8');
136
137
  console.log(`✅ Generated ${middlewarePath}`);
137
138
  }
@@ -44,6 +44,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
44
44
  exports.generateProxy = generateProxy;
45
45
  const fs = __importStar(require("fs"));
46
46
  const path = __importStar(require("path"));
47
+ const os = __importStar(require("os"));
47
48
  /**
48
49
  * Detect if project uses src/ directory structure
49
50
  */
@@ -131,6 +132,6 @@ export const config = {
131
132
  ],
132
133
  };
133
134
  `;
134
- fs.writeFileSync(proxyPath, proxyContent, 'utf-8');
135
+ fs.writeFileSync(proxyPath, proxyContent.replace(/\r?\n/g, os.EOL), 'utf-8');
135
136
  console.log(`✅ Generated ${proxyPath}`);
136
137
  }
package/dist/index.d.ts CHANGED
@@ -24,3 +24,4 @@ export { discoverTables } from './setup-userdata';
24
24
  export { generateMiddleware } from './generate-middleware';
25
25
  export { generateProxy } from './generate-proxy';
26
26
  export { generateHelpers } from './generate-helpers';
27
+ export { generateApiWrapper } from './generate-api-wrapper';
package/dist/index.js CHANGED
@@ -6,12 +6,13 @@
6
6
  * Provides middleware-based database access for Next.js applications.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.generateHelpers = exports.generateProxy = exports.generateMiddleware = exports.discoverTables = void 0;
9
+ exports.generateApiWrapper = exports.generateHelpers = exports.generateProxy = exports.generateMiddleware = exports.discoverTables = void 0;
10
10
  exports.setupNextApiPlugin = setupNextApiPlugin;
11
11
  const setup_userdata_1 = require("./setup-userdata");
12
12
  const generate_middleware_1 = require("./generate-middleware");
13
13
  const generate_proxy_1 = require("./generate-proxy");
14
14
  const generate_helpers_1 = require("./generate-helpers");
15
+ const generate_api_wrapper_1 = require("./generate-api-wrapper");
15
16
  /**
16
17
  * Main setup function for Next.js projects
17
18
  *
@@ -57,6 +58,7 @@ async function setupNextApiPlugin(projectPath, options = {}) {
57
58
  }
58
59
  (0, setup_userdata_1.generateConfigFile)(projectPath, config);
59
60
  (0, generate_helpers_1.generateHelpers)(projectPath);
61
+ (0, generate_api_wrapper_1.generateApiWrapper)(projectPath);
60
62
  (0, setup_userdata_1.updateEnvLocal)(projectPath, config);
61
63
  const proxyFile = useProxy ? 'proxy.ts' : 'middleware.ts';
62
64
  console.log('');
@@ -64,6 +66,7 @@ async function setupNextApiPlugin(projectPath, options = {}) {
64
66
  console.log(` - ${proxyFile} (database proxy)`);
65
67
  console.log(' - egdesk.config.ts (type-safe config)');
66
68
  console.log(' - egdesk-helpers.ts (helper functions)');
69
+ console.log(' - src/lib/api.ts (basePath-aware fetch wrapper)');
67
70
  console.log(' - .env.local (environment variables)');
68
71
  console.log('');
69
72
  console.log('📝 Next steps:');
@@ -72,9 +75,14 @@ async function setupNextApiPlugin(projectPath, options = {}) {
72
75
  console.log(' 3. Import and use helpers in your components:');
73
76
  console.log(' import { queryTable } from "./egdesk-helpers"');
74
77
  console.log(' import { TABLES } from "./egdesk.config"');
78
+ console.log(' import { apiFetch } from "@/lib/api"');
75
79
  console.log('');
76
- console.log('Example usage in a component:');
80
+ console.log('Example usage:');
81
+ console.log(' // Database queries');
77
82
  console.log(' const data = await queryTable(TABLES.table1.name, { limit: 10 });');
83
+ console.log(' ');
84
+ console.log(' // API routes (use apiFetch for basePath support)');
85
+ console.log(' const response = await apiFetch("/api/transactions");');
78
86
  }
79
87
  catch (error) {
80
88
  console.error('❌ Setup failed:', error);
@@ -89,3 +97,5 @@ var generate_proxy_2 = require("./generate-proxy");
89
97
  Object.defineProperty(exports, "generateProxy", { enumerable: true, get: function () { return generate_proxy_2.generateProxy; } });
90
98
  var generate_helpers_2 = require("./generate-helpers");
91
99
  Object.defineProperty(exports, "generateHelpers", { enumerable: true, get: function () { return generate_helpers_2.generateHelpers; } });
100
+ var generate_api_wrapper_2 = require("./generate-api-wrapper");
101
+ Object.defineProperty(exports, "generateApiWrapper", { enumerable: true, get: function () { return generate_api_wrapper_2.generateApiWrapper; } });
@@ -45,6 +45,7 @@ exports.updateEnvLocal = updateEnvLocal;
45
45
  exports.generateConfigFile = generateConfigFile;
46
46
  const fs = __importStar(require("fs"));
47
47
  const path = __importStar(require("path"));
48
+ const os = __importStar(require("os"));
48
49
  /**
49
50
  * Fetch available tables from EGDesk
50
51
  */
@@ -165,7 +166,7 @@ function updateEnvLocal(projectPath, config) {
165
166
  ...config.tables.map((table, index) => `# ${index + 1}. ${table.displayName} (${table.tableName}) - ${table.rowCount} rows, ${table.columnCount} columns`),
166
167
  ''
167
168
  ].join('\n');
168
- fs.writeFileSync(envPath, envContent, 'utf-8');
169
+ fs.writeFileSync(envPath, envContent.replace(/\r?\n/g, os.EOL), 'utf-8');
169
170
  console.log(`✅ Updated ${envPath}`);
170
171
  }
171
172
  /**
@@ -220,6 +221,6 @@ export const TABLE_NAMES = {
220
221
  ${config.tables.map((table, index) => ` table${index + 1}: '${table.tableName}'`).join(',\n')}
221
222
  } as const;
222
223
  `;
223
- fs.writeFileSync(configPath, configContent, 'utf-8');
224
+ fs.writeFileSync(configPath, configContent.replace(/\r?\n/g, os.EOL), 'utf-8');
224
225
  console.log(`✅ Generated ${configPath}`);
225
226
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@egdesk/next-api-plugin",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Next.js plugin for EGDesk database proxy integration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Generate API wrapper for handling basePath in client-side fetch calls
3
+ *
4
+ * Creates src/lib/api.ts that automatically prepends NEXT_PUBLIC_EGDESK_BASE_PATH
5
+ * to relative URLs, solving the Next.js basePath limitation with client-side fetch.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import * as os from 'os';
11
+
12
+ /**
13
+ * Detect if project uses src/ directory structure
14
+ */
15
+ function usesSrcDirectory(projectPath: string): boolean {
16
+ const srcPath = path.join(projectPath, 'src');
17
+ const srcAppPath = path.join(projectPath, 'src', 'app');
18
+ return fs.existsSync(srcPath) && fs.existsSync(srcAppPath);
19
+ }
20
+
21
+ /**
22
+ * Ensure lib directory exists
23
+ */
24
+ function ensureLibDirectory(projectPath: string): string {
25
+ const hasSrc = usesSrcDirectory(projectPath);
26
+ const libDir = hasSrc
27
+ ? path.join(projectPath, 'src', 'lib')
28
+ : path.join(projectPath, 'lib');
29
+
30
+ if (!fs.existsSync(libDir)) {
31
+ fs.mkdirSync(libDir, { recursive: true });
32
+ console.log(`📁 Created directory: ${libDir}`);
33
+ }
34
+
35
+ return libDir;
36
+ }
37
+
38
+ /**
39
+ * Generate src/lib/api.ts file for basePath-aware fetch wrapper
40
+ */
41
+ export function generateApiWrapper(projectPath: string): void {
42
+ const libDir = ensureLibDirectory(projectPath);
43
+ const apiPath = path.join(libDir, 'api.ts');
44
+
45
+ console.log(`📝 Generating API wrapper at: ${apiPath}`);
46
+
47
+ // Check if api.ts already exists
48
+ if (fs.existsSync(apiPath)) {
49
+ console.log('⚠️ api.ts already exists - backing up to api.backup.ts');
50
+ const backupPath = path.join(libDir, 'api.backup.ts');
51
+ fs.copyFileSync(apiPath, backupPath);
52
+ }
53
+
54
+ const apiContent = `/**
55
+ * API Fetch Wrapper for EGDesk Tunneling
56
+ *
57
+ * This wrapper automatically prepends NEXT_PUBLIC_EGDESK_BASE_PATH to relative URLs.
58
+ * This is necessary because Next.js basePath does NOT automatically apply to
59
+ * client-side fetch() calls - it's a known limitation.
60
+ *
61
+ * Generated by @egdesk/next-api-plugin
62
+ */
63
+
64
+ const basePath = process.env.NEXT_PUBLIC_EGDESK_BASE_PATH || '';
65
+
66
+ /**
67
+ * Fetch wrapper that handles basePath for tunneled environments
68
+ *
69
+ * @example
70
+ * // ❌ Wrong - will return 404 in tunneled environment
71
+ * fetch('/api/transactions')
72
+ *
73
+ * @example
74
+ * // ✅ Correct - automatically prepends basePath
75
+ * apiFetch('/api/transactions')
76
+ *
77
+ * @param path - The API path (e.g., '/api/transactions')
78
+ * @param options - Standard fetch options
79
+ */
80
+ export async function apiFetch(path: string, options?: RequestInit): Promise<Response> {
81
+ // Automatically prepend basePath to relative URLs
82
+ const url = path.startsWith('/') && !path.startsWith('//')
83
+ ? \`\${basePath}\${path}\`
84
+ : path;
85
+
86
+ return fetch(url, options);
87
+ }
88
+ `;
89
+
90
+ fs.writeFileSync(apiPath, apiContent.replace(/\r?\n/g, os.EOL), 'utf-8');
91
+ console.log(`✅ Generated ${apiPath}`);
92
+ }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import * as fs from 'fs';
8
8
  import * as path from 'path';
9
+ import * as os from 'os';
9
10
 
10
11
  /**
11
12
  * Generate egdesk-helpers.ts file
@@ -22,27 +23,52 @@ export function generateHelpers(projectPath: string): void {
22
23
  * Generated by @egdesk/next-api-plugin
23
24
  */
24
25
 
26
+ import { EGDESK_CONFIG } from './egdesk.config';
27
+
25
28
  /**
26
29
  * Call EGDesk user-data MCP tool
27
30
  *
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.
31
+ * - Server (API routes): Calls Egdesk API directly using EGDESK_CONFIG.apiUrl and
32
+ * EGDESK_CONFIG.apiKey (from env NEXT_PUBLIC_EGDESK_API_URL / NEXT_PUBLIC_EGDESK_API_KEY)
33
+ * so relative URLs and tunnel base path are not an issue.
34
+ * - Client (browser): Uses /__user_data_proxy so CORS and tunnel base path still work.
30
35
  */
31
36
  export async function callUserDataTool(
32
37
  toolName: string,
33
38
  args: Record<string, any> = {}
34
39
  ): Promise<any> {
40
+ const body = JSON.stringify({ tool: toolName, arguments: args });
35
41
  const headers: Record<string, string> = {
36
42
  'Content-Type': 'application/json'
37
43
  };
38
44
 
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
- });
45
+ const isServer = typeof window === 'undefined';
46
+
47
+ let response: Response;
48
+ if (isServer) {
49
+ // API routes: call Egdesk directly (relative URL is invalid in Node)
50
+ const apiUrl =
51
+ (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_URL) ||
52
+ EGDESK_CONFIG.apiUrl;
53
+ const apiKey =
54
+ (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_KEY) ||
55
+ EGDESK_CONFIG.apiKey;
56
+ if (apiKey) {
57
+ headers['X-Api-Key'] = apiKey;
58
+ }
59
+ response = await fetch(\`\${apiUrl}/user-data/tools/call\`, {
60
+ method: 'POST',
61
+ headers,
62
+ body
63
+ });
64
+ } else {
65
+ // Browser: use proxy for CORS and tunnel base path
66
+ response = await fetch('/__user_data_proxy', {
67
+ method: 'POST',
68
+ headers,
69
+ body
70
+ });
71
+ }
46
72
 
47
73
  if (!response.ok) {
48
74
  throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
@@ -114,7 +140,7 @@ export async function aggregateTable(
114
140
  }
115
141
 
116
142
  /**
117
- * Execute raw SQL query
143
+ * Execute raw SQL query (read-only, SELECT only)
118
144
  */
119
145
  export async function executeSQL(query: string) {
120
146
  return callUserDataTool('user_data_sql_query', { query });
@@ -133,8 +159,102 @@ export async function listTables() {
133
159
  export async function getTableSchema(tableName: string) {
134
160
  return callUserDataTool('user_data_get_schema', { tableName });
135
161
  }
162
+
163
+ /**
164
+ * Create a new table
165
+ */
166
+ export async function createTable(
167
+ displayName: string,
168
+ schema: Array<{
169
+ name: string;
170
+ type: 'TEXT' | 'INTEGER' | 'REAL' | 'DATE';
171
+ notNull?: boolean;
172
+ defaultValue?: any;
173
+ }>,
174
+ options?: {
175
+ description?: string;
176
+ tableName?: string;
177
+ uniqueKeyColumns?: string[];
178
+ duplicateAction?: 'skip' | 'update' | 'allow' | 'replace-date-range';
179
+ }
180
+ ) {
181
+ return callUserDataTool('user_data_create_table', {
182
+ displayName,
183
+ schema,
184
+ ...options
185
+ });
186
+ }
187
+
188
+ /**
189
+ * Insert rows into a table
190
+ */
191
+ export async function insertRows(
192
+ tableName: string,
193
+ rows: Array<Record<string, any>>
194
+ ) {
195
+ return callUserDataTool('user_data_insert_rows', {
196
+ tableName,
197
+ rows
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Update rows in a table
203
+ */
204
+ export async function updateRows(
205
+ tableName: string,
206
+ updates: Record<string, any>,
207
+ options: {
208
+ ids?: number[];
209
+ filters?: Record<string, string>;
210
+ }
211
+ ) {
212
+ return callUserDataTool('user_data_update_rows', {
213
+ tableName,
214
+ updates,
215
+ ...options
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Delete rows from a table
221
+ */
222
+ export async function deleteRows(
223
+ tableName: string,
224
+ options: {
225
+ ids?: number[];
226
+ filters?: Record<string, string>;
227
+ }
228
+ ) {
229
+ return callUserDataTool('user_data_delete_rows', {
230
+ tableName,
231
+ ...options
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Delete a table
237
+ */
238
+ export async function deleteTable(tableName: string) {
239
+ return callUserDataTool('user_data_delete_table', { tableName });
240
+ }
241
+
242
+ /**
243
+ * Rename a table
244
+ */
245
+ export async function renameTable(
246
+ tableName: string,
247
+ newTableName: string,
248
+ newDisplayName?: string
249
+ ) {
250
+ return callUserDataTool('user_data_rename_table', {
251
+ tableName,
252
+ newTableName,
253
+ newDisplayName
254
+ });
255
+ }
136
256
  `;
137
257
 
138
- fs.writeFileSync(helperPath, helperContent, 'utf-8');
258
+ fs.writeFileSync(helperPath, helperContent.replace(/\r?\n/g, os.EOL), 'utf-8');
139
259
  console.log(`✅ Generated ${helperPath}`);
140
260
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
+ import * as os from 'os';
10
11
 
11
12
  /**
12
13
  * Detect if project uses src/ directory structure
@@ -103,6 +104,6 @@ export const config = {
103
104
  };
104
105
  `;
105
106
 
106
- fs.writeFileSync(middlewarePath, middlewareContent, 'utf-8');
107
+ fs.writeFileSync(middlewarePath, middlewareContent.replace(/\r?\n/g, os.EOL), 'utf-8');
107
108
  console.log(`✅ Generated ${middlewarePath}`);
108
109
  }
@@ -9,6 +9,7 @@
9
9
 
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
+ import * as os from 'os';
12
13
 
13
14
  /**
14
15
  * Detect if project uses src/ directory structure
@@ -102,6 +103,6 @@ export const config = {
102
103
  };
103
104
  `;
104
105
 
105
- fs.writeFileSync(proxyPath, proxyContent, 'utf-8');
106
+ fs.writeFileSync(proxyPath, proxyContent.replace(/\r?\n/g, os.EOL), 'utf-8');
106
107
  console.log(`✅ Generated ${proxyPath}`);
107
108
  }
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  import { generateMiddleware } from './generate-middleware';
16
16
  import { generateProxy } from './generate-proxy';
17
17
  import { generateHelpers } from './generate-helpers';
18
+ import { generateApiWrapper } from './generate-api-wrapper';
18
19
 
19
20
  export interface SetupOptions {
20
21
  egdeskUrl?: string;
@@ -75,6 +76,7 @@ export async function setupNextApiPlugin(
75
76
 
76
77
  generateConfigFile(projectPath, config);
77
78
  generateHelpers(projectPath);
79
+ generateApiWrapper(projectPath);
78
80
  updateEnvLocal(projectPath, config);
79
81
 
80
82
  const proxyFile = useProxy ? 'proxy.ts' : 'middleware.ts';
@@ -84,6 +86,7 @@ export async function setupNextApiPlugin(
84
86
  console.log(` - ${proxyFile} (database proxy)`);
85
87
  console.log(' - egdesk.config.ts (type-safe config)');
86
88
  console.log(' - egdesk-helpers.ts (helper functions)');
89
+ console.log(' - src/lib/api.ts (basePath-aware fetch wrapper)');
87
90
  console.log(' - .env.local (environment variables)');
88
91
  console.log('');
89
92
  console.log('📝 Next steps:');
@@ -92,9 +95,14 @@ export async function setupNextApiPlugin(
92
95
  console.log(' 3. Import and use helpers in your components:');
93
96
  console.log(' import { queryTable } from "./egdesk-helpers"');
94
97
  console.log(' import { TABLES } from "./egdesk.config"');
98
+ console.log(' import { apiFetch } from "@/lib/api"');
95
99
  console.log('');
96
- console.log('Example usage in a component:');
100
+ console.log('Example usage:');
101
+ console.log(' // Database queries');
97
102
  console.log(' const data = await queryTable(TABLES.table1.name, { limit: 10 });');
103
+ console.log(' ');
104
+ console.log(' // API routes (use apiFetch for basePath support)');
105
+ console.log(' const response = await apiFetch("/api/transactions");');
98
106
  } catch (error) {
99
107
  console.error('❌ Setup failed:', error);
100
108
  throw error;
@@ -107,3 +115,4 @@ export { discoverTables } from './setup-userdata';
107
115
  export { generateMiddleware } from './generate-middleware';
108
116
  export { generateProxy } from './generate-proxy';
109
117
  export { generateHelpers } from './generate-helpers';
118
+ export { generateApiWrapper } from './generate-api-wrapper';
@@ -7,6 +7,7 @@
7
7
 
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
+ import * as os from 'os';
10
11
 
11
12
  export interface UserDataTable {
12
13
  id: string;
@@ -172,7 +173,7 @@ export function updateEnvLocal(
172
173
  ''
173
174
  ].join('\n');
174
175
 
175
- fs.writeFileSync(envPath, envContent, 'utf-8');
176
+ fs.writeFileSync(envPath, envContent.replace(/\r?\n/g, os.EOL), 'utf-8');
176
177
  console.log(`✅ Updated ${envPath}`);
177
178
  }
178
179
 
@@ -233,6 +234,6 @@ ${config.tables.map((table, index) => ` table${index + 1}: '${table.tableName}'
233
234
  } as const;
234
235
  `;
235
236
 
236
- fs.writeFileSync(configPath, configContent, 'utf-8');
237
+ fs.writeFileSync(configPath, configContent.replace(/\r?\n/g, os.EOL), 'utf-8');
237
238
  console.log(`✅ Generated ${configPath}`);
238
239
  }