@egdesk/next-api-plugin 1.0.0 → 1.2.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/dist/generate-api-wrapper.d.ts +10 -0
- package/dist/generate-api-wrapper.js +118 -0
- package/dist/generate-helpers.js +286 -13
- package/dist/generate-middleware.js +2 -1
- package/dist/generate-proxy.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -2
- package/dist/setup-userdata.js +3 -2
- package/package.json +1 -1
- package/src/generate-api-wrapper.ts +92 -0
- package/src/generate-helpers.ts +286 -13
- package/src/generate-middleware.ts +2 -1
- package/src/generate-proxy.ts +2 -1
- package/src/index.ts +10 -1
- package/src/setup-userdata.ts +3 -2
|
@@ -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
|
+
}
|
package/dist/generate-helpers.js
CHANGED
|
@@ -41,41 +41,67 @@ 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
|
*/
|
|
47
48
|
function generateHelpers(projectPath) {
|
|
48
49
|
const helperPath = path.join(projectPath, 'egdesk-helpers.ts');
|
|
49
50
|
const helperContent = `/**
|
|
50
|
-
* EGDesk
|
|
51
|
+
* EGDesk Helper Functions for Next.js
|
|
51
52
|
*
|
|
52
|
-
* Type-safe helpers for accessing EGDesk user data.
|
|
53
|
+
* Type-safe helpers for accessing EGDesk user data and FinanceHub.
|
|
53
54
|
* Works in both client and server components.
|
|
54
55
|
*
|
|
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
|
-
*
|
|
62
|
-
*
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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,254 @@ 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
|
+
}
|
|
289
|
+
|
|
290
|
+
// ==========================================
|
|
291
|
+
// FINANCEHUB HELPERS
|
|
292
|
+
// ==========================================
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Call EGDesk FinanceHub MCP tool
|
|
296
|
+
*/
|
|
297
|
+
export async function callFinanceHubTool(
|
|
298
|
+
toolName: string,
|
|
299
|
+
args: Record<string, any> = {}
|
|
300
|
+
): Promise<any> {
|
|
301
|
+
const body = JSON.stringify({ tool: toolName, args });
|
|
302
|
+
const headers: Record<string, string> = {
|
|
303
|
+
'Content-Type': 'application/json'
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const isServer = typeof window === 'undefined';
|
|
307
|
+
|
|
308
|
+
let response: Response;
|
|
309
|
+
if (isServer) {
|
|
310
|
+
const apiUrl =
|
|
311
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_URL) ||
|
|
312
|
+
EGDESK_CONFIG.apiUrl;
|
|
313
|
+
const apiKey =
|
|
314
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_KEY) ||
|
|
315
|
+
EGDESK_CONFIG.apiKey;
|
|
316
|
+
if (apiKey) {
|
|
317
|
+
headers['X-Api-Key'] = apiKey;
|
|
318
|
+
}
|
|
319
|
+
response = await fetch(\`\${apiUrl}/financehub/tools/call\`, {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers,
|
|
322
|
+
body
|
|
323
|
+
});
|
|
324
|
+
} else {
|
|
325
|
+
response = await fetch('/__financehub_proxy', {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers,
|
|
328
|
+
body
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!response.ok) {
|
|
333
|
+
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const result = await response.json();
|
|
337
|
+
|
|
338
|
+
if (!result.success) {
|
|
339
|
+
throw new Error(result.error || 'Tool call failed');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const content = result.result?.content?.[0]?.text;
|
|
343
|
+
return content ? JSON.parse(content) : null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* List all registered banks and card companies
|
|
348
|
+
*/
|
|
349
|
+
export async function listBanks() {
|
|
350
|
+
return callFinanceHubTool('financehub_list_banks', {});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* List bank accounts with balances
|
|
355
|
+
*/
|
|
356
|
+
export async function listAccounts(options: {
|
|
357
|
+
bankId?: string;
|
|
358
|
+
isActive?: boolean;
|
|
359
|
+
} = {}) {
|
|
360
|
+
return callFinanceHubTool('financehub_list_accounts', options);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Query bank transactions
|
|
365
|
+
*/
|
|
366
|
+
export async function queryBankTransactions(options: {
|
|
367
|
+
accountId?: string;
|
|
368
|
+
bankId?: string;
|
|
369
|
+
startDate?: string;
|
|
370
|
+
endDate?: string;
|
|
371
|
+
category?: string;
|
|
372
|
+
minAmount?: number;
|
|
373
|
+
maxAmount?: number;
|
|
374
|
+
searchText?: string;
|
|
375
|
+
limit?: number;
|
|
376
|
+
offset?: number;
|
|
377
|
+
orderBy?: 'date' | 'amount' | 'balance';
|
|
378
|
+
orderDir?: 'asc' | 'desc';
|
|
379
|
+
} = {}) {
|
|
380
|
+
return callFinanceHubTool('financehub_query_transactions', options);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Query card transactions (NEW)
|
|
385
|
+
*/
|
|
386
|
+
export async function queryCardTransactions(options: {
|
|
387
|
+
accountId?: string;
|
|
388
|
+
cardCompanyId?: string;
|
|
389
|
+
cardNumber?: string;
|
|
390
|
+
merchantName?: string;
|
|
391
|
+
startDate?: string;
|
|
392
|
+
endDate?: string;
|
|
393
|
+
category?: string;
|
|
394
|
+
minAmount?: number;
|
|
395
|
+
maxAmount?: number;
|
|
396
|
+
includeCancelled?: boolean;
|
|
397
|
+
limit?: number;
|
|
398
|
+
offset?: number;
|
|
399
|
+
orderBy?: 'date' | 'amount';
|
|
400
|
+
orderDir?: 'asc' | 'desc';
|
|
401
|
+
} = {}) {
|
|
402
|
+
return callFinanceHubTool('financehub_query_card_transactions', options);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get transaction statistics
|
|
407
|
+
*/
|
|
408
|
+
export async function getTransactionStats(options: {
|
|
409
|
+
accountId?: string;
|
|
410
|
+
bankId?: string;
|
|
411
|
+
startDate?: string;
|
|
412
|
+
endDate?: string;
|
|
413
|
+
} = {}) {
|
|
414
|
+
return callFinanceHubTool('financehub_get_statistics', options);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get monthly breakdown of deposits/withdrawals
|
|
419
|
+
*/
|
|
420
|
+
export async function getMonthlySummary(options: {
|
|
421
|
+
accountId?: string;
|
|
422
|
+
bankId?: string;
|
|
423
|
+
year?: number;
|
|
424
|
+
months?: number;
|
|
425
|
+
} = {}) {
|
|
426
|
+
return callFinanceHubTool('financehub_get_monthly_summary', options);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Get overall stats (banks, accounts, transactions, balances)
|
|
431
|
+
*/
|
|
432
|
+
export async function getOverallStats() {
|
|
433
|
+
return callFinanceHubTool('financehub_get_overall_stats', {});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Get sync operation history
|
|
438
|
+
*/
|
|
439
|
+
export async function getSyncHistory(limit: number = 50) {
|
|
440
|
+
return callFinanceHubTool('financehub_get_sync_history', { limit });
|
|
441
|
+
}
|
|
169
442
|
`;
|
|
170
|
-
fs.writeFileSync(helperPath, helperContent, 'utf-8');
|
|
443
|
+
fs.writeFileSync(helperPath, helperContent.replace(/\r?\n/g, os.EOL), 'utf-8');
|
|
171
444
|
console.log(`✅ Generated ${helperPath}`);
|
|
172
445
|
}
|
|
@@ -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
|
}
|
package/dist/generate-proxy.js
CHANGED
|
@@ -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
|
|
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; } });
|
package/dist/setup-userdata.js
CHANGED
|
@@ -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
|
@@ -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
|
+
}
|
package/src/generate-helpers.ts
CHANGED
|
@@ -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
|
|
@@ -14,35 +15,60 @@ export function generateHelpers(projectPath: string): void {
|
|
|
14
15
|
const helperPath = path.join(projectPath, 'egdesk-helpers.ts');
|
|
15
16
|
|
|
16
17
|
const helperContent = `/**
|
|
17
|
-
* EGDesk
|
|
18
|
+
* EGDesk Helper Functions for Next.js
|
|
18
19
|
*
|
|
19
|
-
* Type-safe helpers for accessing EGDesk user data.
|
|
20
|
+
* Type-safe helpers for accessing EGDesk user data and FinanceHub.
|
|
20
21
|
* Works in both client and server components.
|
|
21
22
|
*
|
|
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
|
-
*
|
|
29
|
-
*
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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,255 @@ 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
|
+
}
|
|
256
|
+
|
|
257
|
+
// ==========================================
|
|
258
|
+
// FINANCEHUB HELPERS
|
|
259
|
+
// ==========================================
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Call EGDesk FinanceHub MCP tool
|
|
263
|
+
*/
|
|
264
|
+
export async function callFinanceHubTool(
|
|
265
|
+
toolName: string,
|
|
266
|
+
args: Record<string, any> = {}
|
|
267
|
+
): Promise<any> {
|
|
268
|
+
const body = JSON.stringify({ tool: toolName, args });
|
|
269
|
+
const headers: Record<string, string> = {
|
|
270
|
+
'Content-Type': 'application/json'
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const isServer = typeof window === 'undefined';
|
|
274
|
+
|
|
275
|
+
let response: Response;
|
|
276
|
+
if (isServer) {
|
|
277
|
+
const apiUrl =
|
|
278
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_URL) ||
|
|
279
|
+
EGDESK_CONFIG.apiUrl;
|
|
280
|
+
const apiKey =
|
|
281
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_KEY) ||
|
|
282
|
+
EGDESK_CONFIG.apiKey;
|
|
283
|
+
if (apiKey) {
|
|
284
|
+
headers['X-Api-Key'] = apiKey;
|
|
285
|
+
}
|
|
286
|
+
response = await fetch(\`\${apiUrl}/financehub/tools/call\`, {
|
|
287
|
+
method: 'POST',
|
|
288
|
+
headers,
|
|
289
|
+
body
|
|
290
|
+
});
|
|
291
|
+
} else {
|
|
292
|
+
response = await fetch('/__financehub_proxy', {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
headers,
|
|
295
|
+
body
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const result = await response.json();
|
|
304
|
+
|
|
305
|
+
if (!result.success) {
|
|
306
|
+
throw new Error(result.error || 'Tool call failed');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const content = result.result?.content?.[0]?.text;
|
|
310
|
+
return content ? JSON.parse(content) : null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* List all registered banks and card companies
|
|
315
|
+
*/
|
|
316
|
+
export async function listBanks() {
|
|
317
|
+
return callFinanceHubTool('financehub_list_banks', {});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* List bank accounts with balances
|
|
322
|
+
*/
|
|
323
|
+
export async function listAccounts(options: {
|
|
324
|
+
bankId?: string;
|
|
325
|
+
isActive?: boolean;
|
|
326
|
+
} = {}) {
|
|
327
|
+
return callFinanceHubTool('financehub_list_accounts', options);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Query bank transactions
|
|
332
|
+
*/
|
|
333
|
+
export async function queryBankTransactions(options: {
|
|
334
|
+
accountId?: string;
|
|
335
|
+
bankId?: string;
|
|
336
|
+
startDate?: string;
|
|
337
|
+
endDate?: string;
|
|
338
|
+
category?: string;
|
|
339
|
+
minAmount?: number;
|
|
340
|
+
maxAmount?: number;
|
|
341
|
+
searchText?: string;
|
|
342
|
+
limit?: number;
|
|
343
|
+
offset?: number;
|
|
344
|
+
orderBy?: 'date' | 'amount' | 'balance';
|
|
345
|
+
orderDir?: 'asc' | 'desc';
|
|
346
|
+
} = {}) {
|
|
347
|
+
return callFinanceHubTool('financehub_query_transactions', options);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Query card transactions (NEW)
|
|
352
|
+
*/
|
|
353
|
+
export async function queryCardTransactions(options: {
|
|
354
|
+
accountId?: string;
|
|
355
|
+
cardCompanyId?: string;
|
|
356
|
+
cardNumber?: string;
|
|
357
|
+
merchantName?: string;
|
|
358
|
+
startDate?: string;
|
|
359
|
+
endDate?: string;
|
|
360
|
+
category?: string;
|
|
361
|
+
minAmount?: number;
|
|
362
|
+
maxAmount?: number;
|
|
363
|
+
includeCancelled?: boolean;
|
|
364
|
+
limit?: number;
|
|
365
|
+
offset?: number;
|
|
366
|
+
orderBy?: 'date' | 'amount';
|
|
367
|
+
orderDir?: 'asc' | 'desc';
|
|
368
|
+
} = {}) {
|
|
369
|
+
return callFinanceHubTool('financehub_query_card_transactions', options);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get transaction statistics
|
|
374
|
+
*/
|
|
375
|
+
export async function getTransactionStats(options: {
|
|
376
|
+
accountId?: string;
|
|
377
|
+
bankId?: string;
|
|
378
|
+
startDate?: string;
|
|
379
|
+
endDate?: string;
|
|
380
|
+
} = {}) {
|
|
381
|
+
return callFinanceHubTool('financehub_get_statistics', options);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get monthly breakdown of deposits/withdrawals
|
|
386
|
+
*/
|
|
387
|
+
export async function getMonthlySummary(options: {
|
|
388
|
+
accountId?: string;
|
|
389
|
+
bankId?: string;
|
|
390
|
+
year?: number;
|
|
391
|
+
months?: number;
|
|
392
|
+
} = {}) {
|
|
393
|
+
return callFinanceHubTool('financehub_get_monthly_summary', options);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get overall stats (banks, accounts, transactions, balances)
|
|
398
|
+
*/
|
|
399
|
+
export async function getOverallStats() {
|
|
400
|
+
return callFinanceHubTool('financehub_get_overall_stats', {});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get sync operation history
|
|
405
|
+
*/
|
|
406
|
+
export async function getSyncHistory(limit: number = 50) {
|
|
407
|
+
return callFinanceHubTool('financehub_get_sync_history', { limit });
|
|
408
|
+
}
|
|
136
409
|
`;
|
|
137
410
|
|
|
138
|
-
fs.writeFileSync(helperPath, helperContent, 'utf-8');
|
|
411
|
+
fs.writeFileSync(helperPath, helperContent.replace(/\r?\n/g, os.EOL), 'utf-8');
|
|
139
412
|
console.log(`✅ Generated ${helperPath}`);
|
|
140
413
|
}
|
|
@@ -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
|
}
|
package/src/generate-proxy.ts
CHANGED
|
@@ -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
|
|
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';
|
package/src/setup-userdata.ts
CHANGED
|
@@ -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
|
}
|