jsql-neo 5.4.2 → 5.5.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.
Files changed (72) hide show
  1. package/LICENSE +202 -202
  2. package/README.md +8097 -7971
  3. package/bin/jsql +320 -320
  4. package/bin/jsql-server +90 -90
  5. package/bin/jsql-server-control.js +177 -177
  6. package/index.d.ts +385 -357
  7. package/index.js +124 -118
  8. package/lib/ast.js +214 -0
  9. package/lib/btree.js +413 -413
  10. package/lib/cache.js +328 -328
  11. package/lib/client.js +125 -125
  12. package/lib/database.js +1744 -1744
  13. package/lib/date-types.js +182 -182
  14. package/lib/errors.js +89 -89
  15. package/lib/jsql_format.js +497 -497
  16. package/lib/migrate.js +254 -254
  17. package/lib/mod.js +292 -292
  18. package/lib/mongo_server.js +607 -607
  19. package/lib/multiserver.js +140 -140
  20. package/lib/mysql_compat.js +413 -413
  21. package/lib/mysql_server.js +1237 -1237
  22. package/lib/native_client.js +732 -732
  23. package/lib/nedb_compat.js +506 -506
  24. package/lib/pg_server.js +864 -864
  25. package/lib/plugin.js +34 -34
  26. package/lib/query.js +815 -815
  27. package/lib/redis_server.js +571 -571
  28. package/lib/sql.js +3460 -2925
  29. package/lib/sqlite_compat.js +323 -323
  30. package/lib/sqlite_worker.js +264 -264
  31. package/lib/table.js +1304 -1304
  32. package/lib/tui.js +502 -502
  33. package/lib/wasm.js +1 -1
  34. package/lib/wasm_client.js +718 -718
  35. package/lib/web_ui.js +257 -257
  36. package/native/jsql-neo-native.node +0 -0
  37. package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
  38. package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
  39. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
  40. package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
  41. package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
  42. package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
  43. package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
  44. package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
  45. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
  46. package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
  47. package/nativesrc/jsql-neo-core/src/types.rs +93 -93
  48. package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
  49. package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
  50. package/nativesrc/jsql-neo-native/build.rs +3 -3
  51. package/nativesrc/jsql-neo-native/package.json +7 -7
  52. package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
  53. package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
  54. package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
  55. package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
  56. package/package.json +87 -86
  57. package/postinstall.js +158 -158
  58. package/test/btree.test.js +128 -128
  59. package/test/coverage.js +187 -187
  60. package/test/join.test.js +110 -110
  61. package/test/native.test.js +158 -158
  62. package/test/regress-5.1.0.js +271 -271
  63. package/test/smoke.js +58 -58
  64. package/test/sql-parser.test.js +273 -0
  65. package/test/wasm.test.js +110 -110
  66. package/wasm/browser.d.ts +89 -89
  67. package/wasm/browser.mjs +569 -569
  68. package/wasm/browser_bg.mjs +597 -597
  69. package/wasm/jsql_neo_wasm.d.ts +38 -38
  70. package/wasm/jsql_neo_wasm.js +520 -520
  71. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
  72. package/wasm/package.json +11 -11
package/bin/jsql-server CHANGED
@@ -1,90 +1,90 @@
1
- #!/usr/bin/env node
2
- // © Vexify 2026 All Rights Reserved.
3
- // jsql-neo 后台常驻服务入口 — 由 `jsql start` 以 detached 子进程方式拉起。
4
- // 用法: node bin/jsql-server --config <config.json>
5
- // 支持 SIGTERM/SIGINT 优雅关闭。
6
-
7
- const path = require('path');
8
- const fs = require('fs');
9
- const { createMysqlServer } = require('../lib/mysql_server');
10
-
11
- function parseArgs(argv) {
12
- const args = { _: [] };
13
- for (let i = 0; i < argv.length; i++) {
14
- const a = argv[i];
15
- if (a === '--config') { args.config = argv[++i]; }
16
- else if (a.startsWith('--config=')) { args.config = a.slice(9); }
17
- else args._.push(a);
18
- }
19
- return args;
20
- }
21
-
22
- function loadConfig(configPath) {
23
- const cfg = { port: 3306, host: '127.0.0.1' };
24
- if (configPath && fs.existsSync(configPath)) {
25
- Object.assign(cfg, JSON.parse(fs.readFileSync(configPath, 'utf8')));
26
- }
27
- return cfg;
28
- }
29
-
30
- function buildAuth(cfg) {
31
- if (cfg.auth) {
32
- if (typeof cfg.auth !== 'object' || Array.isArray(cfg.auth)) {
33
- throw new Error('config.auth must be an object map { user: "password" | { password, databases } }');
34
- }
35
- return cfg.auth;
36
- }
37
- if (cfg.user != null || cfg.password != null) {
38
- const auth = {};
39
- auth[cfg.user || 'root'] = cfg.password || '';
40
- return auth;
41
- }
42
- return null;
43
- }
44
-
45
- async function main() {
46
- const args = parseArgs(process.argv.slice(2));
47
- const cfg = loadConfig(args.config);
48
- const auth = buildAuth(cfg);
49
- const options = {
50
- port: cfg.port || 3306,
51
- host: cfg.host || '127.0.0.1',
52
- dataDir: cfg.dataDir || null,
53
- maxConnections: cfg.maxConnections,
54
- idleTimeout: cfg.idleTimeout,
55
- handshakeTimeout: cfg.handshakeTimeout,
56
- safety: cfg.safety !== false,
57
- allowComments: cfg.allowComments === true,
58
- noAuth: cfg.noAuth === true,
59
- };
60
- if (auth) options.auth = auth;
61
- if (cfg.onSecurityEvent) options.onSecurityEvent = cfg.onSecurityEvent;
62
-
63
- const server = createMysqlServer(options);
64
-
65
- let closing = false;
66
- const shutdown = (code) => {
67
- if (closing) return;
68
- closing = true;
69
- server.close(() => process.exit(code));
70
- setTimeout(() => process.exit(code), 2000).unref();
71
- };
72
- process.on('SIGTERM', () => shutdown(0));
73
- process.on('SIGINT', () => shutdown(0));
74
-
75
- server.listen(err => {
76
- if (err) {
77
- console.error(`[jsql-server] failed to start: ${err.message}`);
78
- process.exit(1);
79
- return;
80
- }
81
- const addr = server.address;
82
- console.log(`[jsql-server] listening on ${options.host}:${addr ? addr.port : options.port} (dataDir: ${options.dataDir || 'in-memory'})`);
83
- console.log(`[jsql-server] pid ${process.pid}`);
84
- });
85
- }
86
-
87
- main().catch(e => {
88
- console.error(`[jsql-server] ${e.message}`);
89
- process.exit(1);
90
- });
1
+ #!/usr/bin/env node
2
+ // © Vexify 2026 All Rights Reserved.
3
+ // jsql-neo 后台常驻服务入口 — 由 `jsql start` 以 detached 子进程方式拉起。
4
+ // 用法: node bin/jsql-server --config <config.json>
5
+ // 支持 SIGTERM/SIGINT 优雅关闭。
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const { createMysqlServer } = require('../lib/mysql_server');
10
+
11
+ function parseArgs(argv) {
12
+ const args = { _: [] };
13
+ for (let i = 0; i < argv.length; i++) {
14
+ const a = argv[i];
15
+ if (a === '--config') { args.config = argv[++i]; }
16
+ else if (a.startsWith('--config=')) { args.config = a.slice(9); }
17
+ else args._.push(a);
18
+ }
19
+ return args;
20
+ }
21
+
22
+ function loadConfig(configPath) {
23
+ const cfg = { port: 3306, host: '127.0.0.1' };
24
+ if (configPath && fs.existsSync(configPath)) {
25
+ Object.assign(cfg, JSON.parse(fs.readFileSync(configPath, 'utf8')));
26
+ }
27
+ return cfg;
28
+ }
29
+
30
+ function buildAuth(cfg) {
31
+ if (cfg.auth) {
32
+ if (typeof cfg.auth !== 'object' || Array.isArray(cfg.auth)) {
33
+ throw new Error('config.auth must be an object map { user: "password" | { password, databases } }');
34
+ }
35
+ return cfg.auth;
36
+ }
37
+ if (cfg.user != null || cfg.password != null) {
38
+ const auth = {};
39
+ auth[cfg.user || 'root'] = cfg.password || '';
40
+ return auth;
41
+ }
42
+ return null;
43
+ }
44
+
45
+ async function main() {
46
+ const args = parseArgs(process.argv.slice(2));
47
+ const cfg = loadConfig(args.config);
48
+ const auth = buildAuth(cfg);
49
+ const options = {
50
+ port: cfg.port || 3306,
51
+ host: cfg.host || '127.0.0.1',
52
+ dataDir: cfg.dataDir || null,
53
+ maxConnections: cfg.maxConnections,
54
+ idleTimeout: cfg.idleTimeout,
55
+ handshakeTimeout: cfg.handshakeTimeout,
56
+ safety: cfg.safety !== false,
57
+ allowComments: cfg.allowComments === true,
58
+ noAuth: cfg.noAuth === true,
59
+ };
60
+ if (auth) options.auth = auth;
61
+ if (cfg.onSecurityEvent) options.onSecurityEvent = cfg.onSecurityEvent;
62
+
63
+ const server = createMysqlServer(options);
64
+
65
+ let closing = false;
66
+ const shutdown = (code) => {
67
+ if (closing) return;
68
+ closing = true;
69
+ server.close(() => process.exit(code));
70
+ setTimeout(() => process.exit(code), 2000).unref();
71
+ };
72
+ process.on('SIGTERM', () => shutdown(0));
73
+ process.on('SIGINT', () => shutdown(0));
74
+
75
+ server.listen(err => {
76
+ if (err) {
77
+ console.error(`[jsql-server] failed to start: ${err.message}`);
78
+ process.exit(1);
79
+ return;
80
+ }
81
+ const addr = server.address;
82
+ console.log(`[jsql-server] listening on ${options.host}:${addr ? addr.port : options.port} (dataDir: ${options.dataDir || 'in-memory'})`);
83
+ console.log(`[jsql-server] pid ${process.pid}`);
84
+ });
85
+ }
86
+
87
+ main().catch(e => {
88
+ console.error(`[jsql-server] ${e.message}`);
89
+ process.exit(1);
90
+ });
@@ -1,177 +1,177 @@
1
- // © Vexify 2026 All Rights Reserved.
2
- // jsql server start/stop/status — 后台常驻服务控制(对标 `net mysql80 start`)。
3
- // 状态文件放在 ~/.jsql/ 下:server.pid / server.log / server.json(保存启动配置)。
4
-
5
- const fs = require('fs');
6
- const os = require('os');
7
- const path = require('path');
8
- const { spawn } = require('child_process');
9
-
10
- const STATE_DIR = path.join(os.homedir(), '.jsql');
11
- const PID_FILE = path.join(STATE_DIR, 'server.pid');
12
- const LOG_FILE = path.join(STATE_DIR, 'server.log');
13
- const CONF_FILE = path.join(STATE_DIR, 'server.json');
14
-
15
- function ensureStateDir() {
16
- fs.mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
17
- }
18
-
19
- function readPid() {
20
- try {
21
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
22
- return Number.isInteger(pid) && pid > 0 ? pid : null;
23
- } catch (e) {
24
- return null;
25
- }
26
- }
27
-
28
- function isAlive(pid) {
29
- if (!pid) return false;
30
- try {
31
- process.kill(pid, 0);
32
- return true;
33
- } catch (e) {
34
- return e.code === 'EPERM';
35
- }
36
- }
37
-
38
- function isPortOpen(port, host) {
39
- return new Promise(resolve => {
40
- const net = require('net');
41
- const sock = net.connect({ port, host: host || '127.0.0.1' });
42
- sock.once('connect', () => { sock.destroy(); resolve(true); });
43
- sock.once('error', () => resolve(false));
44
- setTimeout(() => { sock.destroy(); resolve(false); }, 800);
45
- });
46
- }
47
-
48
- function parseAuthSpecs(list) {
49
- const auth = {};
50
- for (const spec of list) {
51
- const idx = spec.indexOf(':');
52
- if (idx <= 0) {
53
- auth[spec] = '';
54
- continue;
55
- }
56
- const user = spec.slice(0, idx);
57
- const rest = spec.slice(idx + 1);
58
- const idx2 = rest.indexOf(':');
59
- const password = idx2 === -1 ? rest : rest.slice(0, idx2);
60
- const dbPart = idx2 === -1 ? '' : rest.slice(idx2 + 1);
61
- const dbs = dbPart
62
- ? dbPart.split(',').map(s => s.trim()).filter(Boolean)
63
- : null;
64
- auth[user] = dbs ? { password, databases: dbs } : password;
65
- }
66
- return auth;
67
- }
68
-
69
- async function start(opts = {}) {
70
- ensureStateDir();
71
- const existing = readPid();
72
- if (existing && isAlive(existing)) {
73
- return `jsql server is already running (pid ${existing}).`;
74
- }
75
-
76
- const configPath = opts.config || CONF_FILE;
77
- let cfg = { port: 3306, host: '127.0.0.1' };
78
- if (fs.existsSync(configPath)) {
79
- try { cfg = Object.assign(cfg, JSON.parse(fs.readFileSync(configPath, 'utf8'))); } catch (e) {}
80
- }
81
- if (opts.port != null) cfg.port = opts.port;
82
- if (opts.host != null) cfg.host = opts.host;
83
- if (opts.dataDir != null) cfg.dataDir = path.resolve(opts.dataDir);
84
- if (opts.noAuth) {
85
- cfg.noAuth = true;
86
- delete cfg.auth;
87
- delete cfg.user;
88
- delete cfg.password;
89
- } else {
90
- cfg.noAuth = false;
91
- }
92
- if (opts.auth) cfg.auth = parseAuthSpecs(Array.isArray(opts.auth) ? opts.auth : [opts.auth]);
93
- fs.writeFileSync(CONF_FILE, JSON.stringify(cfg, null, 2), 'utf8');
94
-
95
- const entry = path.join(__dirname, 'jsql-server');
96
- const logFd = fs.openSync(LOG_FILE, 'a');
97
- const child = spawn(process.execPath, [entry, '--config', CONF_FILE], {
98
- detached: true,
99
- stdio: ['ignore', logFd, logFd],
100
- env: Object.assign({}, process.env, { JSQL_SERVER_MAIN: '1' }),
101
- });
102
- child.once('error', err => {
103
- fs.closeSync(logFd);
104
- throw err;
105
- });
106
- child.unref();
107
-
108
- fs.writeFileSync(PID_FILE, String(child.pid), 'utf8');
109
-
110
- const ready = await waitReady(cfg.port, cfg.host, 5000);
111
- if (!ready) {
112
- if (isAlive(child.pid)) {
113
- try { process.kill(child.pid, 'SIGTERM'); } catch (e) {}
114
- fs.unlinkSync(PID_FILE);
115
- }
116
- throw new Error(`server failed to start (see ${LOG_FILE})`);
117
- }
118
- return `jsql server started (pid ${child.pid}, ${cfg.host}:${cfg.port}, dataDir: ${cfg.dataDir || 'in-memory'}). Log: ${LOG_FILE}`;
119
- }
120
-
121
- function waitReady(port, host, timeoutMs) {
122
- const deadline = Date.now() + timeoutMs;
123
- return new Promise(resolve => {
124
- const tick = async () => {
125
- if (await isPortOpen(port, host)) return resolve(true);
126
- if (Date.now() > deadline) return resolve(false);
127
- setTimeout(tick, 200);
128
- };
129
- tick();
130
- });
131
- }
132
-
133
- async function stop(opts = {}) {
134
- const pid = readPid();
135
- if (!pid) return 'jsql server is not running.';
136
- if (!isAlive(pid)) {
137
- fs.unlinkSync(PID_FILE);
138
- return 'jsql server is not running.';
139
- }
140
- try {
141
- process.kill(pid, 'SIGTERM');
142
- } catch (e) {
143
- throw new Error(`failed to stop server (pid ${pid}): ${e.message}`);
144
- }
145
- const deadline = Date.now() + 5000;
146
- while (Date.now() < deadline) {
147
- if (!isAlive(pid)) break;
148
- await new Promise(r => setTimeout(r, 150));
149
- }
150
- if (isAlive(pid)) {
151
- try { process.kill(pid, 'SIGKILL'); } catch (e) {}
152
- }
153
- fs.unlinkSync(PID_FILE);
154
- return `jsql server stopped (pid ${pid}).`;
155
- }
156
-
157
- async function status(opts = {}) {
158
- const pid = readPid();
159
- if (!pid || !isAlive(pid)) {
160
- return 'jsql server is not running.';
161
- }
162
- let cfg = {};
163
- try { cfg = JSON.parse(fs.readFileSync(CONF_FILE, 'utf8')); } catch (e) {}
164
- const port = (opts.port != null ? opts.port : cfg.port) || 3306;
165
- const host = (opts.host != null ? opts.host : cfg.host) || '127.0.0.1';
166
- const up = await isPortOpen(port, host);
167
- const lines = [
168
- `jsql server is running (pid ${pid}).`,
169
- ` ${host}:${port} ${up ? 'listening' : 'NOT responding'}`,
170
- ` dataDir: ${cfg.dataDir || 'in-memory'}`,
171
- ` log: ${LOG_FILE}`,
172
- ];
173
- if (cfg.auth) lines.push(` users: ${Object.keys(cfg.auth).join(', ')}`);
174
- return lines.join('\n');
175
- }
176
-
177
- module.exports = { serverControl: { start, stop, status }, STATE_DIR, PID_FILE, LOG_FILE, CONF_FILE };
1
+ // © Vexify 2026 All Rights Reserved.
2
+ // jsql server start/stop/status — 后台常驻服务控制(对标 `net mysql80 start`)。
3
+ // 状态文件放在 ~/.jsql/ 下:server.pid / server.log / server.json(保存启动配置)。
4
+
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const { spawn } = require('child_process');
9
+
10
+ const STATE_DIR = path.join(os.homedir(), '.jsql');
11
+ const PID_FILE = path.join(STATE_DIR, 'server.pid');
12
+ const LOG_FILE = path.join(STATE_DIR, 'server.log');
13
+ const CONF_FILE = path.join(STATE_DIR, 'server.json');
14
+
15
+ function ensureStateDir() {
16
+ fs.mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
17
+ }
18
+
19
+ function readPid() {
20
+ try {
21
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
22
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
23
+ } catch (e) {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ function isAlive(pid) {
29
+ if (!pid) return false;
30
+ try {
31
+ process.kill(pid, 0);
32
+ return true;
33
+ } catch (e) {
34
+ return e.code === 'EPERM';
35
+ }
36
+ }
37
+
38
+ function isPortOpen(port, host) {
39
+ return new Promise(resolve => {
40
+ const net = require('net');
41
+ const sock = net.connect({ port, host: host || '127.0.0.1' });
42
+ sock.once('connect', () => { sock.destroy(); resolve(true); });
43
+ sock.once('error', () => resolve(false));
44
+ setTimeout(() => { sock.destroy(); resolve(false); }, 800);
45
+ });
46
+ }
47
+
48
+ function parseAuthSpecs(list) {
49
+ const auth = {};
50
+ for (const spec of list) {
51
+ const idx = spec.indexOf(':');
52
+ if (idx <= 0) {
53
+ auth[spec] = '';
54
+ continue;
55
+ }
56
+ const user = spec.slice(0, idx);
57
+ const rest = spec.slice(idx + 1);
58
+ const idx2 = rest.indexOf(':');
59
+ const password = idx2 === -1 ? rest : rest.slice(0, idx2);
60
+ const dbPart = idx2 === -1 ? '' : rest.slice(idx2 + 1);
61
+ const dbs = dbPart
62
+ ? dbPart.split(',').map(s => s.trim()).filter(Boolean)
63
+ : null;
64
+ auth[user] = dbs ? { password, databases: dbs } : password;
65
+ }
66
+ return auth;
67
+ }
68
+
69
+ async function start(opts = {}) {
70
+ ensureStateDir();
71
+ const existing = readPid();
72
+ if (existing && isAlive(existing)) {
73
+ return `jsql server is already running (pid ${existing}).`;
74
+ }
75
+
76
+ const configPath = opts.config || CONF_FILE;
77
+ let cfg = { port: 3306, host: '127.0.0.1' };
78
+ if (fs.existsSync(configPath)) {
79
+ try { cfg = Object.assign(cfg, JSON.parse(fs.readFileSync(configPath, 'utf8'))); } catch (e) {}
80
+ }
81
+ if (opts.port != null) cfg.port = opts.port;
82
+ if (opts.host != null) cfg.host = opts.host;
83
+ if (opts.dataDir != null) cfg.dataDir = path.resolve(opts.dataDir);
84
+ if (opts.noAuth) {
85
+ cfg.noAuth = true;
86
+ delete cfg.auth;
87
+ delete cfg.user;
88
+ delete cfg.password;
89
+ } else {
90
+ cfg.noAuth = false;
91
+ }
92
+ if (opts.auth) cfg.auth = parseAuthSpecs(Array.isArray(opts.auth) ? opts.auth : [opts.auth]);
93
+ fs.writeFileSync(CONF_FILE, JSON.stringify(cfg, null, 2), 'utf8');
94
+
95
+ const entry = path.join(__dirname, 'jsql-server');
96
+ const logFd = fs.openSync(LOG_FILE, 'a');
97
+ const child = spawn(process.execPath, [entry, '--config', CONF_FILE], {
98
+ detached: true,
99
+ stdio: ['ignore', logFd, logFd],
100
+ env: Object.assign({}, process.env, { JSQL_SERVER_MAIN: '1' }),
101
+ });
102
+ child.once('error', err => {
103
+ fs.closeSync(logFd);
104
+ throw err;
105
+ });
106
+ child.unref();
107
+
108
+ fs.writeFileSync(PID_FILE, String(child.pid), 'utf8');
109
+
110
+ const ready = await waitReady(cfg.port, cfg.host, 5000);
111
+ if (!ready) {
112
+ if (isAlive(child.pid)) {
113
+ try { process.kill(child.pid, 'SIGTERM'); } catch (e) {}
114
+ fs.unlinkSync(PID_FILE);
115
+ }
116
+ throw new Error(`server failed to start (see ${LOG_FILE})`);
117
+ }
118
+ return `jsql server started (pid ${child.pid}, ${cfg.host}:${cfg.port}, dataDir: ${cfg.dataDir || 'in-memory'}). Log: ${LOG_FILE}`;
119
+ }
120
+
121
+ function waitReady(port, host, timeoutMs) {
122
+ const deadline = Date.now() + timeoutMs;
123
+ return new Promise(resolve => {
124
+ const tick = async () => {
125
+ if (await isPortOpen(port, host)) return resolve(true);
126
+ if (Date.now() > deadline) return resolve(false);
127
+ setTimeout(tick, 200);
128
+ };
129
+ tick();
130
+ });
131
+ }
132
+
133
+ async function stop(opts = {}) {
134
+ const pid = readPid();
135
+ if (!pid) return 'jsql server is not running.';
136
+ if (!isAlive(pid)) {
137
+ fs.unlinkSync(PID_FILE);
138
+ return 'jsql server is not running.';
139
+ }
140
+ try {
141
+ process.kill(pid, 'SIGTERM');
142
+ } catch (e) {
143
+ throw new Error(`failed to stop server (pid ${pid}): ${e.message}`);
144
+ }
145
+ const deadline = Date.now() + 5000;
146
+ while (Date.now() < deadline) {
147
+ if (!isAlive(pid)) break;
148
+ await new Promise(r => setTimeout(r, 150));
149
+ }
150
+ if (isAlive(pid)) {
151
+ try { process.kill(pid, 'SIGKILL'); } catch (e) {}
152
+ }
153
+ fs.unlinkSync(PID_FILE);
154
+ return `jsql server stopped (pid ${pid}).`;
155
+ }
156
+
157
+ async function status(opts = {}) {
158
+ const pid = readPid();
159
+ if (!pid || !isAlive(pid)) {
160
+ return 'jsql server is not running.';
161
+ }
162
+ let cfg = {};
163
+ try { cfg = JSON.parse(fs.readFileSync(CONF_FILE, 'utf8')); } catch (e) {}
164
+ const port = (opts.port != null ? opts.port : cfg.port) || 3306;
165
+ const host = (opts.host != null ? opts.host : cfg.host) || '127.0.0.1';
166
+ const up = await isPortOpen(port, host);
167
+ const lines = [
168
+ `jsql server is running (pid ${pid}).`,
169
+ ` ${host}:${port} ${up ? 'listening' : 'NOT responding'}`,
170
+ ` dataDir: ${cfg.dataDir || 'in-memory'}`,
171
+ ` log: ${LOG_FILE}`,
172
+ ];
173
+ if (cfg.auth) lines.push(` users: ${Object.keys(cfg.auth).join(', ')}`);
174
+ return lines.join('\n');
175
+ }
176
+
177
+ module.exports = { serverControl: { start, stop, status }, STATE_DIR, PID_FILE, LOG_FILE, CONF_FILE };