jsql-neo 4.0.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/jsql +69 -0
- package/bin/jsql-server +90 -0
- package/bin/jsql-server-control.js +177 -0
- package/lib/database.js +98 -9
- package/lib/errors.js +6 -0
- package/lib/jsql_format.js +33 -14
- package/lib/mod.js +43 -67
- package/lib/mysql_compat.js +134 -17
- package/lib/mysql_server.js +315 -22
- package/lib/native_client.js +95 -32
- package/lib/sql.js +646 -122
- package/lib/table.js +114 -12
- package/lib/wasm_client.js +59 -6
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +455 -0
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +12 -6
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +28 -7
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +22 -1
- package/nativesrc/jsql-neo-core/src/engine/table.rs +53 -23
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -1
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +1 -0
- package/nativesrc/jsql-neo-native/build.rs +3 -0
- package/nativesrc/jsql-neo-native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-native/src/lib.rs +75 -87
- package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -0
- package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -0
- package/nativesrc/jsql-neo-wasm/src/lib.rs +423 -0
- package/package.json +8 -1
- package/wasm/jsql_neo_wasm.d.ts +2 -0
- package/wasm/jsql_neo_wasm.js +5 -0
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +1 -0
package/lib/mod.js
CHANGED
|
@@ -2,7 +2,6 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const Module = require('module');
|
|
5
|
-
const vm = require('vm');
|
|
6
5
|
|
|
7
6
|
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.config', 'jsql', 'mod.config');
|
|
8
7
|
const CONFIG_PATH = process.env.JSQL_MOD_CONFIG || DEFAULT_CONFIG_PATH;
|
|
@@ -90,8 +89,9 @@ class ModuleManager {
|
|
|
90
89
|
|
|
91
90
|
save() {
|
|
92
91
|
const dir = path.dirname(this._configPath);
|
|
93
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
94
93
|
fs.writeFileSync(this._configPath, JSON.stringify({ modules: this._modules }, null, 2), 'utf8');
|
|
94
|
+
try { fs.chmodSync(this._configPath, 0o600); } catch (e) {}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
list() {
|
|
@@ -102,7 +102,7 @@ class ModuleManager {
|
|
|
102
102
|
return this._modules.find(m => m.name === name) || null;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
add(address) {
|
|
105
|
+
add(address, opts = {}) {
|
|
106
106
|
const resolved = path.resolve(address);
|
|
107
107
|
if (!fs.existsSync(resolved)) {
|
|
108
108
|
throw new Error(`Module file not found: ${resolved}`);
|
|
@@ -136,22 +136,30 @@ class ModuleManager {
|
|
|
136
136
|
if (!m) throw new Error(`Module not found: ${name}. Use 'jsql mod add --address <path>' first.`);
|
|
137
137
|
if (m.enabled) return { ...m, ok: true, message: 'already enabled' };
|
|
138
138
|
|
|
139
|
-
const
|
|
139
|
+
const enabling = this._enablingStack || (this._enablingStack = new Set());
|
|
140
|
+
if (enabling.has(name)) throw new Error(`Circular module dependency: ${[...enabling, name].join(' -> ')}`);
|
|
141
|
+
enabling.add(name);
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.
|
|
143
|
+
try {
|
|
144
|
+
const plugin = this._loadModuleFile(m.path);
|
|
145
|
+
|
|
146
|
+
if (plugin.depends && plugin.depends.length > 0) {
|
|
147
|
+
for (const dep of plugin.depends) {
|
|
148
|
+
const depMod = this.find(dep);
|
|
149
|
+
if (!depMod) throw new Error(`Module '${name}' depends on '${dep}' which is not registered`);
|
|
150
|
+
if (!depMod.enabled && opts.withDeps !== false) {
|
|
151
|
+
this.enable(dep, opts);
|
|
152
|
+
}
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
|
-
}
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
m.enabled = true;
|
|
157
|
+
m.priority = plugin.priority || m.priority || 0;
|
|
158
|
+
this.save();
|
|
159
|
+
return { ...m, ok: true };
|
|
160
|
+
} finally {
|
|
161
|
+
enabling.delete(name);
|
|
162
|
+
}
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
disable(name) {
|
|
@@ -207,9 +215,9 @@ class ModuleManager {
|
|
|
207
215
|
const cache = new Map();
|
|
208
216
|
|
|
209
217
|
const resolve = (name, chain) => {
|
|
210
|
-
if (resolved.has(name)) return;
|
|
218
|
+
if (resolved.has(name)) return 0;
|
|
211
219
|
const entry = enabled.find(m => m.name === name);
|
|
212
|
-
if (!entry) return;
|
|
220
|
+
if (!entry) return 0;
|
|
213
221
|
if (chain.has(name)) throw new Error(`Circular module dependency: ${[...chain, name].join(' -> ')}`);
|
|
214
222
|
|
|
215
223
|
let plugin;
|
|
@@ -220,17 +228,21 @@ class ModuleManager {
|
|
|
220
228
|
cache.set(entry.path, plugin);
|
|
221
229
|
}
|
|
222
230
|
|
|
231
|
+
let maxDepDepth = -1;
|
|
223
232
|
for (const dep of (plugin.depends || [])) {
|
|
224
|
-
resolve(dep, new Set([...chain, name]));
|
|
233
|
+
const d = resolve(dep, new Set([...chain, name]));
|
|
234
|
+
maxDepDepth = Math.max(maxDepDepth, d);
|
|
225
235
|
}
|
|
226
236
|
resolved.add(name);
|
|
227
|
-
order.push(entry);
|
|
237
|
+
order.push({ entry, depth: maxDepDepth + 1 });
|
|
238
|
+
return maxDepDepth + 1;
|
|
228
239
|
};
|
|
229
240
|
|
|
230
241
|
for (const entry of enabled) resolve(entry.name, new Set());
|
|
231
242
|
|
|
232
|
-
|
|
233
|
-
|
|
243
|
+
// 依赖深度优先,同层按 priority 排序:保证被依赖方始终先应用
|
|
244
|
+
order.sort((a, b) => a.depth - b.depth || (a.entry.priority || 0) - (b.entry.priority || 0));
|
|
245
|
+
return order.map(o => o.entry);
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
remove(name) {
|
|
@@ -258,57 +270,21 @@ class ModuleManager {
|
|
|
258
270
|
};
|
|
259
271
|
}
|
|
260
272
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const jsqlMod = new ModApi(collector);
|
|
269
|
-
const context = vm.createContext({
|
|
270
|
-
module: sandboxModule,
|
|
271
|
-
exports: sandboxModule.exports,
|
|
272
|
-
require: sandboxRequire,
|
|
273
|
-
__filename: filePath,
|
|
274
|
-
__dirname: path.dirname(filePath),
|
|
275
|
-
console,
|
|
276
|
-
process,
|
|
277
|
-
Buffer,
|
|
278
|
-
setTimeout,
|
|
279
|
-
clearTimeout,
|
|
280
|
-
setInterval,
|
|
281
|
-
clearInterval,
|
|
282
|
-
URL,
|
|
283
|
-
URLSearchParams,
|
|
284
|
-
TextEncoder,
|
|
285
|
-
TextDecoder,
|
|
286
|
-
jsql: { mod: jsqlMod }
|
|
287
|
-
});
|
|
288
|
-
|
|
273
|
+
// JS 模块使用 Node.js require 直接加载(信任模型——模块由管理员通过 CLI 管理,
|
|
274
|
+
// 与 npm 包模型相同,无安全隔离假设)。
|
|
275
|
+
// 如需防止非预期代码执行,请使用 JSON-only 模块(只声明 hooks/api 无代码执行能力)。
|
|
276
|
+
const resolved = path.isAbsolute(filePath) ? filePath : require.resolve(filePath);
|
|
277
|
+
delete require.cache[resolved];
|
|
278
|
+
let plugin;
|
|
289
279
|
try {
|
|
290
|
-
|
|
280
|
+
plugin = require(filePath);
|
|
291
281
|
} catch (e) {
|
|
292
282
|
throw new Error(`Failed to load module ${filePath}: ${e.message}`);
|
|
293
283
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const hasExports = sandboxExports && Object.keys(sandboxExports).length > 0;
|
|
297
|
-
const plugin = hasExports ? sandboxExports : {
|
|
298
|
-
hooks: collector.hooks,
|
|
299
|
-
onEvent: collector.onEvent,
|
|
300
|
-
install: collector.install
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const meta = {
|
|
304
|
-
name: collector.name || plugin.name || path.basename(filePath, ext),
|
|
305
|
-
priority: collector.priority !== undefined ? collector.priority : plugin.priority,
|
|
306
|
-
depends: collector.depends || plugin.depends,
|
|
307
|
-
api: collector.api || plugin.api
|
|
308
|
-
};
|
|
309
|
-
for (const [k, v] of Object.entries(meta)) {
|
|
310
|
-
if (v !== undefined && v !== null) plugin[k] = v;
|
|
284
|
+
if (typeof plugin === 'function') {
|
|
285
|
+
return { install: plugin, name: path.basename(filePath, ext) };
|
|
311
286
|
}
|
|
287
|
+
if (!plugin.name) plugin.name = path.basename(filePath, ext);
|
|
312
288
|
return plugin;
|
|
313
289
|
}
|
|
314
290
|
}
|
package/lib/mysql_compat.js
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
const { executeSQL, applyParams, escapeValue, escapeId } = require('./sql');
|
|
8
8
|
const Database = require('./database');
|
|
9
9
|
|
|
10
|
+
function isQueryResult(r) {
|
|
11
|
+
return !!r && typeof r === 'object' && Array.isArray(r.rows) && Array.isArray(r.columns);
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
function toResultPacket(r, table) {
|
|
11
15
|
if (isQueryResult(r)) {
|
|
12
16
|
const columns = r.columns || [];
|
|
@@ -174,36 +178,149 @@ class Connection {
|
|
|
174
178
|
class Pool {
|
|
175
179
|
constructor(options = {}) {
|
|
176
180
|
this.config = options;
|
|
177
|
-
this.
|
|
181
|
+
this.connectionLimit = options.connectionLimit || 10;
|
|
182
|
+
this.queueLimit = options.queueLimit || 0;
|
|
183
|
+
this.idleTimeout = options.idleTimeout != null ? options.idleTimeout : 300000;
|
|
184
|
+
this._connections = [];
|
|
185
|
+
this._waiters = [];
|
|
186
|
+
this._closed = false;
|
|
187
|
+
this._sharedEngine = null;
|
|
188
|
+
this._reaper = setInterval(() => this._reapIdle(), Math.max(1000, Math.floor(this.idleTimeout / 10) || 10000));
|
|
189
|
+
this._reaper.unref();
|
|
178
190
|
}
|
|
179
191
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
_createConnection() {
|
|
193
|
+
const conn = new Connection(this.config);
|
|
194
|
+
conn._pool = this;
|
|
195
|
+
if (!conn.database && !conn.engine && !conn.filename) {
|
|
196
|
+
conn.database = this._sharedEngine;
|
|
184
197
|
}
|
|
185
|
-
|
|
198
|
+
const origDestroy = conn.destroy.bind(conn);
|
|
199
|
+
conn.destroy = () => {
|
|
200
|
+
this._remove(conn);
|
|
201
|
+
origDestroy();
|
|
202
|
+
};
|
|
203
|
+
return conn;
|
|
186
204
|
}
|
|
187
205
|
|
|
188
|
-
|
|
189
|
-
|
|
206
|
+
async _ensureSharedEngine() {
|
|
207
|
+
if (!this._sharedEngine) {
|
|
208
|
+
this._sharedEngine = new Database(':memory:');
|
|
209
|
+
if (typeof this._sharedEngine.start === 'function') await this._sharedEngine.start();
|
|
210
|
+
}
|
|
211
|
+
return this._sharedEngine;
|
|
190
212
|
}
|
|
191
213
|
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
_remove(conn) {
|
|
215
|
+
const idx = this._connections.indexOf(conn);
|
|
216
|
+
if (idx !== -1) this._connections.splice(idx, 1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_reapIdle() {
|
|
220
|
+
if (this.idleTimeout <= 0) return;
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
for (const conn of this._connections.slice()) {
|
|
223
|
+
if (conn.state === 'released' && !conn._inUse && now - (conn._releasedAt || 0) > this.idleTimeout) {
|
|
224
|
+
this._remove(conn);
|
|
225
|
+
if (conn._ownEngine && conn.engine && typeof conn.engine.stop === 'function') {
|
|
226
|
+
conn.engine.stop().catch(() => {});
|
|
227
|
+
}
|
|
228
|
+
conn.state = 'destroyed';
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_acquire() {
|
|
234
|
+
if (this._closed) return Promise.reject(new Error('Pool is closed'));
|
|
235
|
+
const free = this._connections.find(c => c.state === 'released' && !c._inUse);
|
|
236
|
+
if (free) {
|
|
237
|
+
free._inUse = true;
|
|
238
|
+
free.state = 'acquired';
|
|
239
|
+
return Promise.resolve(free);
|
|
240
|
+
}
|
|
241
|
+
if (this._connections.length < this.connectionLimit) {
|
|
242
|
+
return this._ensureSharedEngine().then(() => {
|
|
243
|
+
if (this._connections.length >= this.connectionLimit) {
|
|
244
|
+
return this._waitForConnection();
|
|
245
|
+
}
|
|
246
|
+
const conn = this._createConnection();
|
|
247
|
+
this._connections.push(conn);
|
|
248
|
+
conn._inUse = true;
|
|
249
|
+
conn.state = 'acquired';
|
|
250
|
+
return conn._getEngine().then(
|
|
251
|
+
() => conn,
|
|
252
|
+
err => {
|
|
253
|
+
this._remove(conn);
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return this._waitForConnection();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_waitForConnection() {
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
this._waiters.push({ resolve, reject });
|
|
265
|
+
if (this.queueLimit > 0 && this._waiters.length > this.queueLimit) {
|
|
266
|
+
const w = this._waiters.shift();
|
|
267
|
+
w.reject(new Error('Pool queue limit exceeded'));
|
|
268
|
+
}
|
|
196
269
|
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_release(conn) {
|
|
273
|
+
conn._inUse = false;
|
|
274
|
+
conn.state = 'released';
|
|
275
|
+
conn._releasedAt = Date.now();
|
|
276
|
+
const w = this._waiters.shift();
|
|
277
|
+
if (w) {
|
|
278
|
+
conn._inUse = true;
|
|
279
|
+
conn.state = 'acquired';
|
|
280
|
+
w.resolve(conn);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async _getConnection() {
|
|
285
|
+
return this._acquire();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async query(...args) {
|
|
289
|
+
const conn = await this._acquire();
|
|
290
|
+
try {
|
|
291
|
+
return await conn.query(...args);
|
|
292
|
+
} finally {
|
|
293
|
+
this._release(conn);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async getConnection(cb) {
|
|
298
|
+
const p = (async () => {
|
|
299
|
+
const conn = await this._acquire();
|
|
300
|
+
let released = false;
|
|
301
|
+
const release = () => {
|
|
302
|
+
if (released) return;
|
|
303
|
+
released = true;
|
|
304
|
+
if (conn.state === 'destroyed') {
|
|
305
|
+
this._remove(conn);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
this._release(conn);
|
|
309
|
+
};
|
|
310
|
+
return { connection: conn, release };
|
|
311
|
+
})();
|
|
197
312
|
if (cb) p.then(r => cb(null, r.connection, r.release), err => cb(err));
|
|
198
313
|
return p;
|
|
199
314
|
}
|
|
200
315
|
|
|
201
|
-
end(cb) {
|
|
316
|
+
async end(cb) {
|
|
202
317
|
const p = (async () => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
318
|
+
this._closed = true;
|
|
319
|
+
if (this._reaper) { clearInterval(this._reaper); this._reaper = null; }
|
|
320
|
+
const err = new Error('Pool is closed');
|
|
321
|
+
for (const w of this._waiters.splice(0)) w.reject(err);
|
|
322
|
+
const conns = this._connections.splice(0);
|
|
323
|
+
await Promise.allSettled(conns.map(c => c.end()));
|
|
207
324
|
return undefined;
|
|
208
325
|
})();
|
|
209
326
|
if (cb) p.then(() => cb(null), err => cb(err));
|