@mostajs/setup 1.4.6 → 1.4.7

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 and JDBC dialect status
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 and JDBC dialect status
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: BridgeManager bridges + PID files + port probing
44
+ // Scan active bridges
16
45
  const bridges = [];
17
- // 1. Check BridgeManager known bridges
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. Scan PID files for orphan bridges
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
- const h = await res.json();
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 for unknown bridges (no PID file)
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
- bridges.push({ port, pid: 0, status: 'active', jdbcUrl: h.jdbcUrl });
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
- // Also check port 9001 if no PID file found
151
+ // Fallback: check port 9001 via TCP
119
152
  if (!hsqldbServer) {
120
- const { execSync } = await import('child_process');
121
- try {
122
- const out = execSync('fuser 9001/tcp 2>/dev/null', { encoding: 'utf-8' }).trim();
123
- if (out) {
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
- try {
211
- const check = await fetch(`http://${host || 'localhost'}:${actualPort}`, { signal: AbortSignal.timeout(500) });
212
- void check;
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 for cleanup
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
- while (Date.now() - startTime < 8000) {
257
- try {
258
- await new Promise(r => setTimeout(r, 500));
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
- catch {
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); // check alive
273
- // Port might be open but not HTTP — that's fine for HSQLDB
274
- serverReady = true;
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
- const { BridgeManager } = await import('@mostajs/orm');
320
- const manager = BridgeManager.getInstance();
321
- const bridges = manager.list();
322
- const bridge = bridges.find((b) => b.port === bridgePort);
323
- if (bridge) {
324
- await manager.stop(bridge.key);
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
- // 2. Kill by PID if provided
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 { /* already dead */ }
351
+ catch { /* dead */ }
332
352
  }
333
- // 3. Fallback: kill process on port
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 { /* port may already be free */ }
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
- const msg = err instanceof Error ? err.message : 'Erreur';
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
- // Default: action === 'start'
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,11 +384,9 @@ 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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "MIT",