@mostajs/setup 1.4.6 → 1.4.8
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.
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates handlers for JAR file management API.
|
|
3
3
|
*
|
|
4
|
-
* GET — list uploaded JARs
|
|
4
|
+
* GET — list uploaded JARs, JDBC dialect status, active bridges, HSQLDB server status
|
|
5
5
|
* POST — upload a new JAR file (multipart/form-data)
|
|
6
6
|
* DELETE — remove a JAR file
|
|
7
|
+
* PATCH — start/stop HSQLDB server and JDBC bridge
|
|
7
8
|
*/
|
|
8
9
|
export declare function createUploadJarHandlers(): {
|
|
9
10
|
GET: () => Promise<Response>;
|
|
@@ -1,20 +1,49 @@
|
|
|
1
1
|
// @mostajs/setup — API Route factory for JAR file upload/list/delete
|
|
2
2
|
// Delegates to @mostajs/orm jar-upload module
|
|
3
3
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
/**
|
|
5
|
+
* Check if a TCP port is open (non-HTTP — raw socket connect).
|
|
6
|
+
*/
|
|
7
|
+
async function isPortOpen(port, host = 'localhost', timeoutMs = 800) {
|
|
8
|
+
const net = await import('net');
|
|
9
|
+
return new Promise(resolve => {
|
|
10
|
+
const socket = new net.Socket();
|
|
11
|
+
socket.setTimeout(timeoutMs);
|
|
12
|
+
socket.once('connect', () => { socket.destroy(); resolve(true); });
|
|
13
|
+
socket.once('timeout', () => { socket.destroy(); resolve(false); });
|
|
14
|
+
socket.once('error', () => { socket.destroy(); resolve(false); });
|
|
15
|
+
socket.connect(port, host);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get PID of process listening on a port via fuser.
|
|
20
|
+
*/
|
|
21
|
+
async function getPidOnPort(port) {
|
|
22
|
+
try {
|
|
23
|
+
const { execSync } = await import('child_process');
|
|
24
|
+
const out = execSync(`fuser ${port}/tcp 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
25
|
+
const pid = parseInt(out);
|
|
26
|
+
return isNaN(pid) ? 0 : pid;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
4
32
|
/**
|
|
5
33
|
* Creates handlers for JAR file management API.
|
|
6
34
|
*
|
|
7
|
-
* GET — list uploaded JARs
|
|
35
|
+
* GET — list uploaded JARs, JDBC dialect status, active bridges, HSQLDB server status
|
|
8
36
|
* POST — upload a new JAR file (multipart/form-data)
|
|
9
37
|
* DELETE — remove a JAR file
|
|
38
|
+
* PATCH — start/stop HSQLDB server and JDBC bridge
|
|
10
39
|
*/
|
|
11
40
|
export function createUploadJarHandlers() {
|
|
12
41
|
async function GET() {
|
|
13
42
|
try {
|
|
14
43
|
const { listJarFiles, getJdbcDialectStatus } = await import('@mostajs/orm');
|
|
15
|
-
// Scan active bridges
|
|
44
|
+
// Scan active bridges
|
|
16
45
|
const bridges = [];
|
|
17
|
-
// 1.
|
|
46
|
+
// 1. BridgeManager known bridges
|
|
18
47
|
try {
|
|
19
48
|
const { BridgeManager } = await import('@mostajs/orm');
|
|
20
49
|
const manager = BridgeManager.getInstance();
|
|
@@ -23,7 +52,7 @@ export function createUploadJarHandlers() {
|
|
|
23
52
|
}
|
|
24
53
|
}
|
|
25
54
|
catch { /* ignore */ }
|
|
26
|
-
// 2.
|
|
55
|
+
// 2. PID files for orphan bridges
|
|
27
56
|
try {
|
|
28
57
|
const { existsSync, readdirSync, readFileSync } = await import('fs');
|
|
29
58
|
const { join } = await import('path');
|
|
@@ -35,14 +64,12 @@ export function createUploadJarHandlers() {
|
|
|
35
64
|
if (!portMatch)
|
|
36
65
|
continue;
|
|
37
66
|
const port = parseInt(portMatch[1]);
|
|
38
|
-
// Skip if already known from BridgeManager
|
|
39
67
|
if (bridges.some(b => b.port === port))
|
|
40
68
|
continue;
|
|
41
69
|
const pidStr = readFileSync(join(jarDir, file), 'utf-8').trim();
|
|
42
70
|
const pid = parseInt(pidStr);
|
|
43
71
|
if (isNaN(pid))
|
|
44
72
|
continue;
|
|
45
|
-
// Check if process is alive
|
|
46
73
|
let alive = false;
|
|
47
74
|
try {
|
|
48
75
|
process.kill(pid, 0);
|
|
@@ -50,23 +77,28 @@ export function createUploadJarHandlers() {
|
|
|
50
77
|
}
|
|
51
78
|
catch { /* dead */ }
|
|
52
79
|
if (alive) {
|
|
53
|
-
// Probe health
|
|
54
80
|
let jdbcUrl;
|
|
55
81
|
try {
|
|
56
82
|
const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(1000) });
|
|
57
83
|
if (res.ok) {
|
|
58
|
-
|
|
59
|
-
jdbcUrl = h.jdbcUrl;
|
|
84
|
+
jdbcUrl = (await res.json()).jdbcUrl;
|
|
60
85
|
}
|
|
61
86
|
}
|
|
62
87
|
catch { /* not responding */ }
|
|
63
88
|
bridges.push({ port, pid, status: jdbcUrl ? 'active' : 'orphan', jdbcUrl });
|
|
64
89
|
}
|
|
90
|
+
else {
|
|
91
|
+
// Clean stale PID file
|
|
92
|
+
try {
|
|
93
|
+
(await import('fs')).unlinkSync(join(jarDir, file));
|
|
94
|
+
}
|
|
95
|
+
catch { /* ignore */ }
|
|
96
|
+
}
|
|
65
97
|
}
|
|
66
98
|
}
|
|
67
99
|
}
|
|
68
100
|
catch { /* ignore */ }
|
|
69
|
-
// 3. Probe common bridge ports
|
|
101
|
+
// 3. Probe common bridge ports (HTTP — bridges speak HTTP)
|
|
70
102
|
const basePort = parseInt(process.env.MOSTA_BRIDGE_PORT_BASE || '8765');
|
|
71
103
|
for (let port = basePort; port < basePort + 5; port++) {
|
|
72
104
|
if (bridges.some(b => b.port === port))
|
|
@@ -74,18 +106,20 @@ export function createUploadJarHandlers() {
|
|
|
74
106
|
try {
|
|
75
107
|
const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(500) });
|
|
76
108
|
if (res.ok) {
|
|
77
|
-
const h = await res.json();
|
|
78
|
-
|
|
109
|
+
const h = (await res.json());
|
|
110
|
+
const pid = await getPidOnPort(port);
|
|
111
|
+
bridges.push({ port, pid, status: 'active', jdbcUrl: h.jdbcUrl });
|
|
79
112
|
}
|
|
80
113
|
}
|
|
81
114
|
catch { /* not a bridge */ }
|
|
82
115
|
}
|
|
83
|
-
// Check HSQLDB server status
|
|
116
|
+
// Check HSQLDB server status (TCP probe, NOT HTTP)
|
|
84
117
|
let hsqldbServer = null;
|
|
85
118
|
try {
|
|
86
119
|
const { existsSync, readdirSync, readFileSync } = await import('fs');
|
|
87
120
|
const { join } = await import('path');
|
|
88
121
|
const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
|
|
122
|
+
// Check PID files first
|
|
89
123
|
if (existsSync(jarDir)) {
|
|
90
124
|
const serverPidFiles = readdirSync(jarDir).filter((f) => f.startsWith('.hsqldb-server-') && f.endsWith('.pid'));
|
|
91
125
|
for (const file of serverPidFiles) {
|
|
@@ -107,7 +141,6 @@ export function createUploadJarHandlers() {
|
|
|
107
141
|
hsqldbServer = { running: true, port, pid };
|
|
108
142
|
}
|
|
109
143
|
else {
|
|
110
|
-
// Clean stale PID file
|
|
111
144
|
try {
|
|
112
145
|
(await import('fs')).unlinkSync(join(jarDir, file));
|
|
113
146
|
}
|
|
@@ -115,17 +148,13 @@ export function createUploadJarHandlers() {
|
|
|
115
148
|
}
|
|
116
149
|
}
|
|
117
150
|
}
|
|
118
|
-
//
|
|
151
|
+
// Fallback: check port 9001 via TCP
|
|
119
152
|
if (!hsqldbServer) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
const pid = parseInt(out);
|
|
125
|
-
hsqldbServer = { running: true, port: 9001, pid: isNaN(pid) ? 0 : pid };
|
|
126
|
-
}
|
|
153
|
+
const open = await isPortOpen(9001);
|
|
154
|
+
if (open) {
|
|
155
|
+
const pid = await getPidOnPort(9001);
|
|
156
|
+
hsqldbServer = { running: true, port: 9001, pid };
|
|
127
157
|
}
|
|
128
|
-
catch { /* not running */ }
|
|
129
158
|
}
|
|
130
159
|
}
|
|
131
160
|
catch { /* ignore */ }
|
|
@@ -206,13 +235,11 @@ export function createUploadJarHandlers() {
|
|
|
206
235
|
return Response.json({ ok: false, error: 'start-server supporte uniquement hsqldb' }, { status: 400 });
|
|
207
236
|
}
|
|
208
237
|
const actualPort = sgbdPort || 9001;
|
|
209
|
-
// Check if already running
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
return Response.json({ ok: true, message: `Serveur HSQLDB deja en marche sur le port ${actualPort}`, port: actualPort, alreadyRunning: true });
|
|
238
|
+
// Check if already running (TCP probe, not HTTP)
|
|
239
|
+
if (await isPortOpen(actualPort, host || 'localhost')) {
|
|
240
|
+
const pid = await getPidOnPort(actualPort);
|
|
241
|
+
return Response.json({ ok: true, message: `Serveur HSQLDB deja en marche sur le port ${actualPort}`, port: actualPort, pid, alreadyRunning: true });
|
|
214
242
|
}
|
|
215
|
-
catch { /* not running — proceed */ }
|
|
216
243
|
// Find JAR
|
|
217
244
|
const { JdbcNormalizer } = await import('@mostajs/orm');
|
|
218
245
|
const jarPath = JdbcNormalizer.findJar('hsqldb');
|
|
@@ -239,7 +266,7 @@ export function createUploadJarHandlers() {
|
|
|
239
266
|
});
|
|
240
267
|
serverProc.unref();
|
|
241
268
|
const serverPid = serverProc.pid || 0;
|
|
242
|
-
// Save PID file
|
|
269
|
+
// Save PID file
|
|
243
270
|
const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
|
|
244
271
|
if (existsSync(jarDir)) {
|
|
245
272
|
writeFileSync(join(jarDir, `.hsqldb-server-${actualPort}.pid`), String(serverPid));
|
|
@@ -250,30 +277,21 @@ export function createUploadJarHandlers() {
|
|
|
250
277
|
if (msg)
|
|
251
278
|
console.error(`[HSQLDB:server] ${msg}`);
|
|
252
279
|
});
|
|
253
|
-
// Wait for server to be ready
|
|
254
|
-
const startTime = Date.now();
|
|
280
|
+
// Wait for server to be ready (TCP probe)
|
|
255
281
|
let serverReady = false;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const sock = await fetch(`http://localhost:${actualPort}`, { signal: AbortSignal.timeout(500) });
|
|
260
|
-
void sock;
|
|
282
|
+
for (let i = 0; i < 16; i++) {
|
|
283
|
+
await new Promise(r => setTimeout(r, 500));
|
|
284
|
+
if (await isPortOpen(actualPort, host || 'localhost')) {
|
|
261
285
|
serverReady = true;
|
|
262
286
|
break;
|
|
263
287
|
}
|
|
264
|
-
|
|
265
|
-
// HSQLDB server doesn't speak HTTP, but we can check if the port is listening
|
|
266
|
-
// Try a TCP connection test via the bridge health check pattern
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
// Alternative: check if process is still alive and port is open
|
|
270
|
-
if (!serverReady) {
|
|
288
|
+
// Also check process is still alive
|
|
271
289
|
try {
|
|
272
|
-
process.kill(serverPid, 0);
|
|
273
|
-
|
|
274
|
-
|
|
290
|
+
process.kill(serverPid, 0);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
break; /* process died */
|
|
275
294
|
}
|
|
276
|
-
catch { /* process died */ }
|
|
277
295
|
}
|
|
278
296
|
if (!serverReady) {
|
|
279
297
|
return Response.json({ ok: false, error: 'Le serveur HSQLDB n\'a pas demarre dans le delai imparti' });
|
|
@@ -311,31 +329,33 @@ export function createUploadJarHandlers() {
|
|
|
311
329
|
return Response.json({ ok: false, error: err instanceof Error ? err.message : 'Erreur' });
|
|
312
330
|
}
|
|
313
331
|
}
|
|
332
|
+
// ── Stop bridge ──
|
|
314
333
|
if (action === 'stop') {
|
|
315
334
|
const bridgePort = body.port || 8765;
|
|
316
335
|
const bridgePid = body.pid || 0;
|
|
317
336
|
try {
|
|
318
337
|
// 1. Try BridgeManager
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
338
|
+
try {
|
|
339
|
+
const { BridgeManager } = await import('@mostajs/orm');
|
|
340
|
+
const manager = BridgeManager.getInstance();
|
|
341
|
+
const bridge = manager.list().find((b) => b.port === bridgePort);
|
|
342
|
+
if (bridge)
|
|
343
|
+
await manager.stop(bridge.key);
|
|
325
344
|
}
|
|
326
|
-
|
|
345
|
+
catch { /* ignore */ }
|
|
346
|
+
// 2. Kill by PID
|
|
327
347
|
if (bridgePid > 0) {
|
|
328
348
|
try {
|
|
329
349
|
process.kill(bridgePid, 'SIGKILL');
|
|
330
350
|
}
|
|
331
|
-
catch { /*
|
|
351
|
+
catch { /* dead */ }
|
|
332
352
|
}
|
|
333
|
-
// 3. Fallback:
|
|
334
|
-
const { execSync } = await import('child_process');
|
|
353
|
+
// 3. Fallback: fuser
|
|
335
354
|
try {
|
|
355
|
+
const { execSync } = await import('child_process');
|
|
336
356
|
execSync(`fuser -k ${bridgePort}/tcp 2>/dev/null`, { stdio: 'ignore' });
|
|
337
357
|
}
|
|
338
|
-
catch { /*
|
|
358
|
+
catch { /* already free */ }
|
|
339
359
|
// 4. Clean PID file
|
|
340
360
|
try {
|
|
341
361
|
const { existsSync, unlinkSync } = await import('fs');
|
|
@@ -348,16 +368,14 @@ export function createUploadJarHandlers() {
|
|
|
348
368
|
return Response.json({ ok: true, message: `Bridge arrete (port ${bridgePort})` });
|
|
349
369
|
}
|
|
350
370
|
catch (err) {
|
|
351
|
-
|
|
352
|
-
return Response.json({ ok: false, error: msg });
|
|
371
|
+
return Response.json({ ok: false, error: err instanceof Error ? err.message : 'Erreur' });
|
|
353
372
|
}
|
|
354
373
|
}
|
|
355
|
-
//
|
|
374
|
+
// ── Start bridge ──
|
|
356
375
|
const { dialect, host, port, name, user, password } = body;
|
|
357
376
|
if (!dialect) {
|
|
358
377
|
return Response.json({ ok: false, error: 'dialect requis' }, { status: 400 });
|
|
359
378
|
}
|
|
360
|
-
// Compose URI using setup's composeDbUri for consistency with test-db
|
|
361
379
|
const { composeDbUri } = await import('../lib/compose-uri');
|
|
362
380
|
const uri = composeDbUri(dialect, {
|
|
363
381
|
host: host || 'localhost',
|
|
@@ -366,17 +384,31 @@ export function createUploadJarHandlers() {
|
|
|
366
384
|
user: user || '',
|
|
367
385
|
password: password || '',
|
|
368
386
|
});
|
|
369
|
-
// Start bridge via BridgeManager directly (no SELECT 1 — just start the Java process)
|
|
370
387
|
const { BridgeManager } = await import('@mostajs/orm');
|
|
371
388
|
const { JdbcNormalizer } = await import('@mostajs/orm');
|
|
372
389
|
const manager = BridgeManager.getInstance();
|
|
373
|
-
// Check if JAR is available
|
|
374
390
|
if (!JdbcNormalizer.isAvailable(dialect)) {
|
|
375
391
|
return Response.json({
|
|
376
392
|
ok: false,
|
|
377
393
|
error: `Aucun JAR JDBC trouve pour ${dialect}. Uploadez le JAR d'abord.`,
|
|
378
394
|
});
|
|
379
395
|
}
|
|
396
|
+
// For dialects that require a running server, check reachability first
|
|
397
|
+
const SERVER_DIALECTS = {
|
|
398
|
+
hsqldb: 9001, oracle: 1521, db2: 50000, mssql: 1433,
|
|
399
|
+
};
|
|
400
|
+
const defaultSgbdPort = SERVER_DIALECTS[dialect];
|
|
401
|
+
if (defaultSgbdPort) {
|
|
402
|
+
const sgbdPort = port || defaultSgbdPort;
|
|
403
|
+
const sgbdHost = host || 'localhost';
|
|
404
|
+
const reachable = await isPortOpen(sgbdPort, sgbdHost, 2000);
|
|
405
|
+
if (!reachable) {
|
|
406
|
+
return Response.json({
|
|
407
|
+
ok: false,
|
|
408
|
+
error: `Serveur ${dialect.toUpperCase()} non accessible sur ${sgbdHost}:${sgbdPort}. Demarrez le serveur d'abord.`,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
380
412
|
const bridge = await manager.getOrCreate(dialect, uri);
|
|
381
413
|
return Response.json({ ok: true, port: bridge.port });
|
|
382
414
|
}
|
package/package.json
CHANGED