jsql-neo 6.4.0-beta.11 → 6.4.0-beta.13
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/README.md +1 -1
- package/index.js +1 -1
- package/lib/database.js +9 -0
- package/lib/multiserver.js +5 -4
- package/lib/sql.js +122 -9
- package/package.json +1 -1
- package/postinstall.js +39 -8
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **One engine to rule them all** — a Rust-powered embedded database that speaks your language:
|
|
4
4
|
> MySQL, PostgreSQL, MongoDB, Redis, SQL, TypeScript, **and the browser**.
|
|
5
5
|
|
|
6
|
-
> **v6.4.0-beta.
|
|
6
|
+
> **v6.4.0-beta.13** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|

|
package/index.js
CHANGED
package/lib/database.js
CHANGED
|
@@ -2406,6 +2406,15 @@ class Database {
|
|
|
2406
2406
|
this._savepointStack = [];
|
|
2407
2407
|
if (!snap) return;
|
|
2408
2408
|
this._restoreTxSnapshot(snap);
|
|
2409
|
+
// 修复 B1:rollback 后必须落盘,否则事务期间已 flush 到磁盘的写入
|
|
2410
|
+
// 不会被撤销,kill -9 重启后数据不一致(ACID 原子性被破坏)。
|
|
2411
|
+
// dirMode 下事务写入已逐表 flush,rollback 后需重新 flush 恢复后的数据;
|
|
2412
|
+
// 非 dirMode 下 commitTx 会 save(),rollback 也要 save() 保持一致。
|
|
2413
|
+
if (this._dirMode) {
|
|
2414
|
+
try { this._flushDirty(); } catch (e) { this._emit('error', e); }
|
|
2415
|
+
} else if (!this._memoryMode) {
|
|
2416
|
+
this.save();
|
|
2417
|
+
}
|
|
2409
2418
|
}
|
|
2410
2419
|
|
|
2411
2420
|
// ============================================================
|
package/lib/multiserver.js
CHANGED
|
@@ -21,10 +21,8 @@ const { MongoServer } = require('./mongo_server');
|
|
|
21
21
|
|
|
22
22
|
function sniffProtocol(buf) {
|
|
23
23
|
if (buf.length < 1) return null;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (b === 0x0a || b === 0x0d) return 'mysql';
|
|
27
|
-
// MongoDB: header 前 4 字节 = int32LE 长度,第 13 字节 = opCode(OP_QUERY 2004 / OP_COMPRESSED 2012 / OP_MSG 2013)
|
|
24
|
+
// MongoDB 先检查:opCode 校验更具体,避免消息长度为 256 倍数时
|
|
25
|
+
// 首字节 0x00 被误判为 PG(PG 也用 0x00 开头)
|
|
28
26
|
if (buf.length >= 16) {
|
|
29
27
|
const opc = buf.readInt32LE(12);
|
|
30
28
|
if (opc === 2012 || opc === 2013 || opc === 2004) {
|
|
@@ -32,6 +30,9 @@ function sniffProtocol(buf) {
|
|
|
32
30
|
if (len >= 16 && len <= 48 * 1024 * 1024) return 'mongo';
|
|
33
31
|
}
|
|
34
32
|
}
|
|
33
|
+
const b = buf[0];
|
|
34
|
+
if (b === 0x00) return 'pg';
|
|
35
|
+
if (b === 0x0a || b === 0x0d) return 'mysql';
|
|
35
36
|
return 'redis';
|
|
36
37
|
}
|
|
37
38
|
|
package/lib/sql.js
CHANGED
|
@@ -392,6 +392,7 @@ class Parser {
|
|
|
392
392
|
this.pos = 0;
|
|
393
393
|
this.paramCount = 0; // 顺序占位符 ? 的序号(从 0 开始)
|
|
394
394
|
this.subquerySeq = 0; // FROM 子查询无别名时自动生成 __subN
|
|
395
|
+
this.depth = 0; // 子查询递归深度
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
peek(offset = 0) { return this.tokens[this.pos + offset]; }
|
|
@@ -1220,6 +1221,8 @@ class Parser {
|
|
|
1220
1221
|
}
|
|
1221
1222
|
|
|
1222
1223
|
parseSelect() {
|
|
1224
|
+
this.depth++;
|
|
1225
|
+
if (this.depth > 200) throw new Error('Subquery nesting too deep (max 200)');
|
|
1223
1226
|
this.expectKeyword('SELECT');
|
|
1224
1227
|
let distinct = false;
|
|
1225
1228
|
if (this.isKeyword('DISTINCT')) { this.next(); distinct = true; }
|
|
@@ -1390,9 +1393,18 @@ class Parser {
|
|
|
1390
1393
|
if (this.isKeyword('LIMIT')) {
|
|
1391
1394
|
this.next();
|
|
1392
1395
|
limit = this.parseValue();
|
|
1396
|
+
if (typeof limit === 'bigint') limit = Number(limit <= BigInt(Number.MAX_SAFE_INTEGER) ? limit : BigInt(Number.MAX_SAFE_INTEGER));
|
|
1393
1397
|
if (typeof limit === 'number' && limit < 0) throw new Error(`LIMIT must be a non-negative integer, got ${limit}`);
|
|
1394
|
-
if (this.isKeyword('OFFSET')) { this.next(); offset = this.parseValue(); if (typeof offset === 'number' && offset < 0) throw new Error(`OFFSET must be a non-negative integer, got ${offset}`); }
|
|
1395
|
-
else if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); offset = limit; limit = this.parseValue(); if (typeof limit === 'number' && limit < 0) throw new Error(`LIMIT must be a non-negative integer, got ${limit}`); }
|
|
1398
|
+
if (this.isKeyword('OFFSET')) { this.next(); offset = this.parseValue(); if (typeof offset === 'bigint') offset = Number(offset <= BigInt(Number.MAX_SAFE_INTEGER) ? offset : BigInt(Number.MAX_SAFE_INTEGER)); if (typeof offset === 'number' && offset < 0) throw new Error(`OFFSET must be a non-negative integer, got ${offset}`); }
|
|
1399
|
+
else if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); offset = limit; limit = this.parseValue(); if (typeof limit === 'bigint') limit = Number(limit <= BigInt(Number.MAX_SAFE_INTEGER) ? limit : BigInt(Number.MAX_SAFE_INTEGER)); if (typeof limit === 'number' && limit < 0) throw new Error(`LIMIT must be a non-negative integer, got ${limit}`); }
|
|
1400
|
+
}
|
|
1401
|
+
// OFFSET without LIMIT (MySQL compatible)
|
|
1402
|
+
if (limit === null && this.isKeyword('OFFSET')) {
|
|
1403
|
+
this.next();
|
|
1404
|
+
offset = this.parseValue();
|
|
1405
|
+
if (typeof offset === 'bigint') offset = Number(offset <= BigInt(Number.MAX_SAFE_INTEGER) ? offset : BigInt(Number.MAX_SAFE_INTEGER));
|
|
1406
|
+
if (typeof offset === 'number' && offset < 0) throw new Error(`OFFSET must be a non-negative integer, got ${offset}`);
|
|
1407
|
+
limit = Number.MAX_SAFE_INTEGER;
|
|
1396
1408
|
}
|
|
1397
1409
|
// 集合运算:UNION [ALL|DISTINCT] / INTERSECT [ALL|DISTINCT] / EXCEPT [ALL|DISTINCT]
|
|
1398
1410
|
let union = null, intersect = null, except = null;
|
|
@@ -1421,13 +1433,15 @@ class Parser {
|
|
|
1421
1433
|
if (this.isWord('FIRST') || this.isWord('NEXT')) this.next();
|
|
1422
1434
|
else throw new Error(`Expected FIRST or NEXT after FETCH, got '${this.peek().value}'`);
|
|
1423
1435
|
const nTok = this.next();
|
|
1424
|
-
|
|
1436
|
+
let n = nTok.type === 'number' ? nTok.value : Number(nTok.value);
|
|
1437
|
+
if (typeof n === 'bigint') n = Number(n <= BigInt(Number.MAX_SAFE_INTEGER) ? n : BigInt(Number.MAX_SAFE_INTEGER));
|
|
1425
1438
|
if (!Number.isFinite(n)) throw new Error(`Expected row count after FETCH FIRST, got '${nTok.value}'`);
|
|
1426
1439
|
limit = n;
|
|
1427
1440
|
if (this.isWord('ROW') || this.isWord('ROWS')) this.next();
|
|
1428
1441
|
if (this.isWord('ONLY')) this.next();
|
|
1429
1442
|
}
|
|
1430
1443
|
this.optionalTailSemicolon();
|
|
1444
|
+
this.depth--;
|
|
1431
1445
|
|
|
1432
1446
|
return { type: 'select', columns, aggregate, distinct, from, where, groupBy, having, orderBy, limit, offset, union, intersect, except, rollup };
|
|
1433
1447
|
}
|
|
@@ -1534,6 +1548,7 @@ class Parser {
|
|
|
1534
1548
|
}
|
|
1535
1549
|
let where = null;
|
|
1536
1550
|
if (this.isKeyword('WHERE')) { this.next(); where = this.parseExpr(); }
|
|
1551
|
+
else console.warn(`⚠️ UPDATE without WHERE clause — this will modify ALL rows in '${table}'`);
|
|
1537
1552
|
const returning = this.parseOptionalReturning();
|
|
1538
1553
|
this.optionalTailSemicolon();
|
|
1539
1554
|
return { type: 'update', table, assignments, where, returning };
|
|
@@ -1562,6 +1577,7 @@ class Parser {
|
|
|
1562
1577
|
if (this.isKeyword('LIMIT')) {
|
|
1563
1578
|
this.next();
|
|
1564
1579
|
limit = this.parseValue();
|
|
1580
|
+
if (typeof limit === 'bigint') limit = Number(limit <= BigInt(Number.MAX_SAFE_INTEGER) ? limit : BigInt(Number.MAX_SAFE_INTEGER));
|
|
1565
1581
|
if (typeof limit === 'number' && limit < 0) throw new Error(`LIMIT must be a non-negative integer, got ${limit}`);
|
|
1566
1582
|
}
|
|
1567
1583
|
const returning = this.parseOptionalReturning();
|
|
@@ -3605,6 +3621,71 @@ function applyScalarFunction(fnNode, row, ctx) {
|
|
|
3605
3621
|
return v.toString(16);
|
|
3606
3622
|
});
|
|
3607
3623
|
case 'DATABASE': case 'SCHEMA': return row && row.__db !== undefined ? row.__db : 'default';
|
|
3624
|
+
// ===== JSON 函数(B2 修复:此前未实现,静默返回 NULL)=====
|
|
3625
|
+
case 'JSON_OBJECT': {
|
|
3626
|
+
// JSON_OBJECT(k1, v1, k2, v2, ...) → { k1: v1, k2: v2 }
|
|
3627
|
+
const obj = {};
|
|
3628
|
+
for (let i = 0; i + 1 < args.length; i += 2) {
|
|
3629
|
+
const k = String(args[i] == null ? '' : args[i]);
|
|
3630
|
+
const v = args[i + 1];
|
|
3631
|
+
obj[k] = v === undefined ? null : v;
|
|
3632
|
+
}
|
|
3633
|
+
return obj;
|
|
3634
|
+
}
|
|
3635
|
+
case 'JSON_ARRAY': {
|
|
3636
|
+
// JSON_ARRAY(v1, v2, ...) → [v1, v2]
|
|
3637
|
+
return args.map(v => v === undefined ? null : v);
|
|
3638
|
+
}
|
|
3639
|
+
case 'JSON_EXTRACT': {
|
|
3640
|
+
// JSON_EXTRACT(json, '$.key' or '$.a.b') → 值
|
|
3641
|
+
const json = args[0];
|
|
3642
|
+
const path = args[1] == null ? '' : String(args[1]);
|
|
3643
|
+
if (json == null || path == null) return null;
|
|
3644
|
+
// 解析 JSON 字符串或对象
|
|
3645
|
+
let data = json;
|
|
3646
|
+
if (typeof json === 'string') {
|
|
3647
|
+
try { data = JSON.parse(json); } catch (e) { return null; }
|
|
3648
|
+
}
|
|
3649
|
+
// 解析 JSONPath: $.key 或 $.a.b 或 $.a[0].b
|
|
3650
|
+
const m = path.match(/^\$\.?([^.\[]+)/);
|
|
3651
|
+
if (!m) {
|
|
3652
|
+
// 尝试 $ 开头后直接跟 key
|
|
3653
|
+
if (path === '$') return data;
|
|
3654
|
+
return null;
|
|
3655
|
+
}
|
|
3656
|
+
const firstKey = m[1];
|
|
3657
|
+
if (firstKey === '') return data;
|
|
3658
|
+
let current = data;
|
|
3659
|
+
// 逐段解析路径
|
|
3660
|
+
const segments = path.replace(/^\$/, '').split('.');
|
|
3661
|
+
for (const seg of segments) {
|
|
3662
|
+
if (current == null) return null;
|
|
3663
|
+
// 处理数组索引: key[0] 或 [0]
|
|
3664
|
+
const arrMatch = seg.match(/^([^\[]*)\[(\d+)\]$/);
|
|
3665
|
+
if (arrMatch) {
|
|
3666
|
+
const key = arrMatch[1];
|
|
3667
|
+
const idx = parseInt(arrMatch[2], 10);
|
|
3668
|
+
if (key) current = current[key];
|
|
3669
|
+
if (Array.isArray(current)) current = current[idx];
|
|
3670
|
+
else return null;
|
|
3671
|
+
} else {
|
|
3672
|
+
if (typeof current !== 'object' || current === null) return null;
|
|
3673
|
+
current = current[seg];
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
return current === undefined ? null : current;
|
|
3677
|
+
}
|
|
3678
|
+
case 'JSON_CONTAINS': {
|
|
3679
|
+
// JSON_CONTAINS(json, value) → 1 or 0
|
|
3680
|
+
const json = args[0];
|
|
3681
|
+
const val = args[1];
|
|
3682
|
+
if (json == null) return null;
|
|
3683
|
+
let data = json;
|
|
3684
|
+
if (typeof json === 'string') {
|
|
3685
|
+
try { data = JSON.parse(json); } catch (e) { return null; }
|
|
3686
|
+
}
|
|
3687
|
+
return JSON.stringify(data).includes(JSON.stringify(val)) ? 1 : 0;
|
|
3688
|
+
}
|
|
3608
3689
|
default:
|
|
3609
3690
|
return null;
|
|
3610
3691
|
}
|
|
@@ -6160,7 +6241,30 @@ class SQLExecutor {
|
|
|
6160
6241
|
all = [{ _virtual: true }];
|
|
6161
6242
|
} else {
|
|
6162
6243
|
const items = statement.from.tables.concat(statement.from.joins.map(j => j.item));
|
|
6163
|
-
|
|
6244
|
+
// B5 修复:自连接(同表多次出现且无别名)时,用 t1/t2 等自动别名(MySQL 惯例),
|
|
6245
|
+
// 而非直接用表名。不同表无别名时仍用表名,保持与 SQL 查询中的表名限定一致。
|
|
6246
|
+
const tableCount = new Map();
|
|
6247
|
+
for (const item of items) {
|
|
6248
|
+
if (item.table) {
|
|
6249
|
+
const c = (tableCount.get(item.table) || 0) + 1;
|
|
6250
|
+
tableCount.set(item.table, c);
|
|
6251
|
+
}
|
|
6252
|
+
}
|
|
6253
|
+
const autoAliasMap = new Map();
|
|
6254
|
+
let autoIdx = 0;
|
|
6255
|
+
const prefix = item => {
|
|
6256
|
+
if (item.alias) return item.alias;
|
|
6257
|
+
if (item.subquery) return item.subquery.alias || item.alias;
|
|
6258
|
+
// 无别名且同表出现多次时生成 t1, t2;否则用表名
|
|
6259
|
+
if (item.table && (tableCount.get(item.table) || 0) > 1) {
|
|
6260
|
+
if (!autoAliasMap.has(item)) {
|
|
6261
|
+
autoIdx++;
|
|
6262
|
+
autoAliasMap.set(item, `t${autoIdx}`);
|
|
6263
|
+
}
|
|
6264
|
+
return autoAliasMap.get(item);
|
|
6265
|
+
}
|
|
6266
|
+
return item.table;
|
|
6267
|
+
};
|
|
6164
6268
|
|
|
6165
6269
|
// 读第一表
|
|
6166
6270
|
const firstItem = statement.from.tables[0];
|
|
@@ -6191,10 +6295,10 @@ class SQLExecutor {
|
|
|
6191
6295
|
// 递归 CTE 迭代中:自身引用绑定到已累积的行
|
|
6192
6296
|
rowsAll = { rows: firstItem._virtualRows.map(r => Object.assign({}, r)), schema: null, columns: null };
|
|
6193
6297
|
} else {
|
|
6194
|
-
rowsAll = await this._readTable(firstItem.table, statement, firstItem
|
|
6298
|
+
rowsAll = await this._readTable(firstItem.table, statement, prefix(firstItem));
|
|
6195
6299
|
}
|
|
6196
6300
|
schema = rowsAll.schema;
|
|
6197
|
-
const firstPrefix = firstItem
|
|
6301
|
+
const firstPrefix = prefix(firstItem);
|
|
6198
6302
|
// 虚拟行分两种,语义不同:
|
|
6199
6303
|
// - _virtualRows:递归 CTE **迭代过程内部**,FROM 是自身引用,列名不带前缀
|
|
6200
6304
|
// (否则递归项里的 `n+1` 取不到值)
|
|
@@ -6222,8 +6326,8 @@ class SQLExecutor {
|
|
|
6222
6326
|
? { rows: (j.item._virtualRows || j.item.subquery._virtualSource).map(r => Object.assign({}, r)) }
|
|
6223
6327
|
: j.item.subquery
|
|
6224
6328
|
? await this.executeSelect(j.item.subquery)
|
|
6225
|
-
: await this._readTable(j.item.table, statement, j.item
|
|
6226
|
-
const rightPrefix = j.item
|
|
6329
|
+
: await this._readTable(j.item.table, statement, prefix(j.item));
|
|
6330
|
+
const rightPrefix = prefix(j.item);
|
|
6227
6331
|
// 迭代内部的虚拟行不加前缀;物化后的派生表要加(与普通子查询一致)
|
|
6228
6332
|
const jRaw = j.item._virtualRows
|
|
6229
6333
|
? j.item._virtualRows
|
|
@@ -6499,7 +6603,16 @@ class SQLExecutor {
|
|
|
6499
6603
|
|
|
6500
6604
|
if (statement.limit !== null) {
|
|
6501
6605
|
const start = statement.offset || 0;
|
|
6502
|
-
|
|
6606
|
+
// B5 修复:超大 LIMIT 时 start + limit 可能超过 Number.MAX_SAFE_INTEGER,
|
|
6607
|
+
// 导致 JS 精度丢失,slice 行为异常。这里加友好错误处理。
|
|
6608
|
+
if (statement.limit > rows.length - start) {
|
|
6609
|
+
// LIMIT 超过实际行数是合法的,直接返回全部剩余行
|
|
6610
|
+
rows = rows.slice(start);
|
|
6611
|
+
} else if (start + statement.limit > Number.MAX_SAFE_INTEGER) {
|
|
6612
|
+
throw new Error(`LIMIT value ${statement.limit} is too large; maximum safe value is ${Number.MAX_SAFE_INTEGER - start}`);
|
|
6613
|
+
} else {
|
|
6614
|
+
rows = rows.slice(start, start + statement.limit);
|
|
6615
|
+
}
|
|
6503
6616
|
}
|
|
6504
6617
|
|
|
6505
6618
|
// 无分组、无聚合的正常输出
|
package/package.json
CHANGED
package/postinstall.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { execSync } = require('child_process');
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
const fs = require('fs');
|
|
3
4
|
const path = require('path');
|
|
4
5
|
const http = require('http');
|
|
@@ -18,20 +19,16 @@ const NATIVE_PATH = path.join(ROOT, 'native', 'jsql-neo-native.node');
|
|
|
18
19
|
// https://<host>/https://github.com/owner/repo/releases/download/vX.Y.Z/asset
|
|
19
20
|
// 鉴权走 `Authorization: Bearer <token>` 请求头(Worker 从该头读取,不读 ?token=)。
|
|
20
21
|
// 前缀可用 JSQL_GHPROXY 覆盖(逗号分隔多个,按序回退;'' 表示直连兜底);
|
|
21
|
-
// token
|
|
22
|
+
// token 仅从环境变量 JSQL_GHPROXY_TOKEN 读取,不再内嵌硬编码 token。
|
|
22
23
|
// 加速站候选(按优先级):自建站优先,公共站回退,最后直连兜底。
|
|
23
24
|
const DEFAULT_PROXIES = [
|
|
24
25
|
'https://gh-proxyjsql-neoworkerapi.vexify.de5.net/',
|
|
25
26
|
'https://gh-proxy.com/',
|
|
26
27
|
];
|
|
27
|
-
// 内置 token:让所有安装者都自动走自建加速站,无需配置环境变量。
|
|
28
|
-
// 加速站自身已限制只能代理 /releases/download/ 等文件下载路径,故该值公开无风险。
|
|
29
|
-
const DEFAULT_GHPROXY_TOKEN =
|
|
30
|
-
'NkUbD165V_1iPvEeGRcAfNEv4cqv5WRLn1NfjgBzIMpo9gq5CBH10Rl5ZPmt7RGCh3kyaZU4Nc6pLhHHbvzc9w';
|
|
31
28
|
const GHPROXY = process.env.JSQL_GHPROXY !== undefined
|
|
32
29
|
? process.env.JSQL_GHPROXY
|
|
33
30
|
: DEFAULT_PROXIES[0];
|
|
34
|
-
const GHPROXY_TOKEN = process.env.JSQL_GHPROXY_TOKEN ||
|
|
31
|
+
const GHPROXY_TOKEN = process.env.JSQL_GHPROXY_TOKEN || '';
|
|
35
32
|
const TAG = `v${pkg.version}`;
|
|
36
33
|
|
|
37
34
|
/** 加速站鉴权头 */
|
|
@@ -39,6 +36,32 @@ function proxyHeaders() {
|
|
|
39
36
|
return GHPROXY_TOKEN ? { Authorization: 'Bearer ' + GHPROXY_TOKEN } : undefined;
|
|
40
37
|
}
|
|
41
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 验证文件 SHA256 校验和。
|
|
41
|
+
* 从环境变量 JSQL_NATIVE_SHA256 读取期望值(hex 编码,64 字符)。
|
|
42
|
+
* 若环境变量未设置则跳过校验(向后兼容),但打印警告。
|
|
43
|
+
*/
|
|
44
|
+
function verifyChecksum(filePath) {
|
|
45
|
+
const expected = process.env.JSQL_NATIVE_SHA256 || '';
|
|
46
|
+
if (!expected) {
|
|
47
|
+
console.warn('[jsql-neo] WARNING: JSQL_NATIVE_SHA256 not set, skipping checksum verification');
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const actual = crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
52
|
+
if (actual !== expected.toLowerCase()) {
|
|
53
|
+
console.error(`[jsql-neo] checksum mismatch: expected ${expected}, got ${actual}`);
|
|
54
|
+
try { fs.unlinkSync(filePath); } catch (_) {}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
console.log('[jsql-neo] checksum verified');
|
|
58
|
+
return true;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.warn('[jsql-neo] checksum verification failed:', e.message);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
66
|
* 生成候选下载地址列表(按优先级):
|
|
44
67
|
* JSQL_GHPROXY 指定的代理 → 内置加速站 → 直连
|
|
@@ -143,6 +166,10 @@ async function downloadAndVerify() {
|
|
|
143
166
|
try {
|
|
144
167
|
// 加速站鉴权走 Authorization 头,直连时不带
|
|
145
168
|
await download(cand.url, NATIVE_PATH, cand.viaProxy ? proxyHeaders() : undefined);
|
|
169
|
+
if (!verifyChecksum(NATIVE_PATH)) {
|
|
170
|
+
console.warn('[jsql-neo] checksum mismatch; trying next source.');
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
146
173
|
if (tryLoadNative()) {
|
|
147
174
|
console.log('[jsql-neo] native module downloaded: native/jsql-neo-native.node');
|
|
148
175
|
return true;
|
|
@@ -156,11 +183,15 @@ async function downloadAndVerify() {
|
|
|
156
183
|
}
|
|
157
184
|
|
|
158
185
|
async function ensureNativeNode() {
|
|
159
|
-
// 已存在且能加载 →
|
|
186
|
+
// 已存在且能加载 → 直接用;否则删掉并重新获取
|
|
160
187
|
if (tryLoadNative()) {
|
|
161
188
|
console.log('[jsql-neo] native module ready: native/jsql-neo-native.node');
|
|
162
189
|
return;
|
|
163
190
|
}
|
|
191
|
+
// 下载前检查是否已存在(即使不能加载也先校验,避免重复下载)
|
|
192
|
+
if (fs.existsSync(NATIVE_PATH)) {
|
|
193
|
+
try { fs.unlinkSync(NATIVE_PATH); } catch (_) {}
|
|
194
|
+
}
|
|
164
195
|
if (await downloadAndVerify()) return;
|
|
165
196
|
buildNativeFromSource();
|
|
166
197
|
}
|
|
@@ -200,4 +231,4 @@ async function main() {
|
|
|
200
231
|
ensureServerBinary();
|
|
201
232
|
}
|
|
202
233
|
|
|
203
|
-
main();
|
|
234
|
+
main();
|