jsql-neo 6.3.3 → 6.3.5
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 +60 -20
- package/index.js +73 -6
- package/lib/btree.js +5 -41
- package/lib/database.js +276 -35
- package/lib/native_client.js +57 -21
- package/lib/sql.js +304 -25
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +8 -1
- package/nativesrc/jsql-neo-native/src/lib.rs +274 -178
- package/package.json +6 -5
- package/test/dml-constraints.test.js +163 -0
- package/test/mysql-compat-hijack.test.js +87 -0
- package/test/native-multi-instance.test.js +139 -0
- package/test/readme-audit.test.js +25 -0
- package/test/sql-parser.test.js +189 -0
- package/test/wal-recovery.test.js +161 -0
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. The browser. **And it fits in one npm package.**
|
|
5
5
|
|
|
6
|
-
> **v6.
|
|
6
|
+
> **v6.3.5** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|

|
|
@@ -233,7 +233,7 @@ npm install && npm run build # option 3: from source
|
|
|
233
233
|
Verify:
|
|
234
234
|
|
|
235
235
|
```bash
|
|
236
|
-
node -e "console.log(require('jsql-neo/package.json').version)" # 6.
|
|
236
|
+
node -e "console.log(require('jsql-neo/package.json').version)" # 6.3.5
|
|
237
237
|
```
|
|
238
238
|
|
|
239
239
|
### 30-second demo
|
|
@@ -1470,7 +1470,7 @@ jsql mod --engine wasm # switch engine (restart required)
|
|
|
1470
1470
|
|
|
1471
1471
|
```bash
|
|
1472
1472
|
$ jsql version
|
|
1473
|
-
jsql-neo v6.
|
|
1473
|
+
jsql-neo v6.3.5
|
|
1474
1474
|
engine: native (napi) | wasm | js
|
|
1475
1475
|
node: v22.0.0 platform: linux x64
|
|
1476
1476
|
```
|
|
@@ -1491,7 +1491,7 @@ jsql tui --memory -q # memory mode, quiet
|
|
|
1491
1491
|
jsql tui --prompt 'db> ' --no-color
|
|
1492
1492
|
```
|
|
1493
1493
|
|
|
1494
|
-
The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=6.
|
|
1494
|
+
The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=6.3.5`.
|
|
1495
1495
|
|
|
1496
1496
|
### Keyboard shortcuts
|
|
1497
1497
|
|
|
@@ -1705,10 +1705,27 @@ db.createIndex('orders', ['status', 'created_at']);
|
|
|
1705
1705
|
|
|
1706
1706
|
### WAL & crash recovery
|
|
1707
1707
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
-
|
|
1711
|
-
-
|
|
1708
|
+
> **Requires an explicit opt-in:** `new Database(path, { wal: true })`. Off by default.
|
|
1709
|
+
|
|
1710
|
+
- Every write is appended to a **write-ahead log** (`<file>.wal`) *before* the snapshot is written
|
|
1711
|
+
- The log is **append-only JSONL** with `fsync` per record — a crash mid-write can lose at most the last record, never the whole log
|
|
1712
|
+
- Logged operations: `createTable` / `dropTable` / `insert` / `update` / `removeById` (transaction markers are recorded for observability)
|
|
1713
|
+
- `save()` / `flush()` / auto-save: write snapshot → **on success** clear the log (checkpoint)
|
|
1714
|
+
- Startup: load last snapshot → **replay the log in order** → checkpoint immediately
|
|
1715
|
+
- A half-written trailing record (torn write) is discarded; the rest still replays
|
|
1716
|
+
- Recovery checkpoints to disk immediately, so a second crash cannot lose replayed data
|
|
1717
|
+
|
|
1718
|
+
**Honest limits:**
|
|
1719
|
+
|
|
1720
|
+
- Rows in tables **without a primary key** are not logged (replay could not deduplicate them). Add a primary key if you need crash recovery on a table.
|
|
1721
|
+
- `fsync` is best-effort; on filesystems that reject it the log may sit in page cache.
|
|
1722
|
+
- This is a **single-process** durability guarantee. It does not cover power loss on exotic storage hardware, nor concurrent writes from multiple processes (a `.lock` file guards the latter).
|
|
1723
|
+
|
|
1724
|
+
```js
|
|
1725
|
+
const db = new Database('./data.jsql', { wal: true });
|
|
1726
|
+
await db.insert('users', { id: 1, name: 'Alice' });
|
|
1727
|
+
// crash here (kill -9) → data is still recovered on next open
|
|
1728
|
+
```
|
|
1712
1729
|
|
|
1713
1730
|
### Snapshots & compression
|
|
1714
1731
|
|
|
@@ -4158,7 +4175,7 @@ Data dir: /root/.jsql-neo/data
|
|
|
4158
4175
|
|
|
4159
4176
|
```bash
|
|
4160
4177
|
$ jsql version
|
|
4161
|
-
jsql-neo v6.
|
|
4178
|
+
jsql-neo v6.3.5
|
|
4162
4179
|
engine: native (napi) | wasm | js
|
|
4163
4180
|
node: v22.0.0
|
|
4164
4181
|
platform: linux x64
|
|
@@ -4603,16 +4620,39 @@ db.createIndex('orders', ['status', 'created_at']);
|
|
|
4603
4620
|
|
|
4604
4621
|
### WAL 与崩溃恢复 WAL & crash recovery
|
|
4605
4622
|
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4623
|
+
> **需显式开启**:`new Database(path, { wal: true })`,默认关闭。
|
|
4624
|
+
>
|
|
4625
|
+
> 6.3.4 起本节描述的是**真实实现**。此前(≤6.3.3)文档承诺的「重放日志恢复」
|
|
4626
|
+
> 并不存在 —— `_recoverFromWAL()` 只删日志不回放,且构造函数在数据文件不存在时
|
|
4627
|
+
> 直接短路,导致开启 `wal:true` 后 `kill -9` 必然丢数据。已修复并补齐回归测试。
|
|
4628
|
+
|
|
4629
|
+
- 每次写操作先 **append 追加**到预写日志(`<file>.wal`),再落快照
|
|
4630
|
+
- 日志为 **append-only JSONL**,每条记录 `fsync`:崩溃最多丢最后一条,不会损坏整个日志
|
|
4631
|
+
- 记录的操作:`createTable` / `dropTable` / `insert` / `update` / `removeById`
|
|
4632
|
+
(事务标记 `begin/commit/rollback` 仅记录以供观测)
|
|
4633
|
+
- `save()` / `flush()` / 自动保存:**写快照成功后才**清空日志(检查点)
|
|
4634
|
+
- 启动流程:加载最后快照 → **按顺序回放日志** → 立即做检查点落盘
|
|
4635
|
+
- 崩溃写半的尾行会被丢弃,其余记录照常回放
|
|
4636
|
+
- 回放后立即落盘,因此二次崩溃不会再次丢失已回放的数据
|
|
4637
|
+
|
|
4638
|
+
**诚实说明边界**:
|
|
4639
|
+
|
|
4640
|
+
- **无主键的表不记录行数据**(回放时无法去重,可能产生重复行)。需要崩溃恢复的表请加主键。
|
|
4641
|
+
- `fsync` 为 best-effort;部分文件系统不支持时日志可能仍在页缓存中。
|
|
4642
|
+
- 这是**单进程**持久性保证,不覆盖特殊硬件下的掉电,也不支持多进程并发写
|
|
4643
|
+
(后者由 `.lock` 文件互斥保护)。
|
|
4610
4644
|
|
|
4611
4645
|
```
|
|
4612
4646
|
启动流程:
|
|
4613
|
-
加载最后快照 ──► 检测
|
|
4614
|
-
│
|
|
4615
|
-
└── 无
|
|
4647
|
+
加载最后快照 ──► 检测 WAL ──► 有? ──► 按序回放 ──► 立即检查点 ──► 就绪
|
|
4648
|
+
│ │
|
|
4649
|
+
└── 无 ────────────────────────┘
|
|
4650
|
+
```
|
|
4651
|
+
|
|
4652
|
+
```js
|
|
4653
|
+
const db = new Database('./data.jsql', { wal: true });
|
|
4654
|
+
await db.insert('users', { id: 1, name: 'Alice' });
|
|
4655
|
+
// 在此 kill -9 → 下次打开数据仍在
|
|
4616
4656
|
```
|
|
4617
4657
|
|
|
4618
4658
|
### 快照与压缩 Snapshots & compression
|
|
@@ -5235,7 +5275,7 @@ Apache License
|
|
|
5235
5275
|
|
|
5236
5276
|
*JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
|
|
5237
5277
|
|
|
5238
|
-
*文档版本:v6.
|
|
5278
|
+
*文档版本:v6.3.5 · 最后更新:2026-09-27*
|
|
5239
5279
|
|
|
5240
5280
|
---
|
|
5241
5281
|
|
|
@@ -7007,7 +7047,7 @@ Usage: jsql version
|
|
|
7007
7047
|
|
|
7008
7048
|
输出版本与环境信息:
|
|
7009
7049
|
|
|
7010
|
-
jsql-neo v6.
|
|
7050
|
+
jsql-neo v6.3.5
|
|
7011
7051
|
engine: native (napi) | wasm | js
|
|
7012
7052
|
node: v22.0.0
|
|
7013
7053
|
platform: linux x64
|
|
@@ -7663,7 +7703,7 @@ CI(GitHub Actions)矩阵:`node 20/22` × `linux/macos/windows` × `native/
|
|
|
7663
7703
|
|
|
7664
7704
|
*JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
|
|
7665
7705
|
|
|
7666
|
-
*文档版本:v6.
|
|
7706
|
+
*文档版本:v6.3.5 · 共 19 个附录 · 最后更新:2026-09-27*
|
|
7667
7707
|
|
|
7668
7708
|
---
|
|
7669
7709
|
|
|
@@ -8270,4 +8310,4 @@ npm test
|
|
|
8270
8310
|
|
|
8271
8311
|
*JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
|
|
8272
8312
|
|
|
8273
|
-
*文档版本:v6.
|
|
8313
|
+
*文档版本:v6.3.5 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-09-27*
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSQL-NEO v6.
|
|
2
|
+
* JSQL-NEO v6.3.5 — Rust-Powered Embedded Database (WASM + HTTP)
|
|
3
3
|
*
|
|
4
4
|
* @example
|
|
5
5
|
* const jsql = require('jsql-neo');
|
|
@@ -30,10 +30,46 @@ const { TUIShell, createTUI } = require('./lib/tui');
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* 全局注入:把项目内 `require('mysql2')` 全部替换为 jsql-neo 内存引擎兼容层。
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* 该函数**不再**改写 `require.cache`。
|
|
34
|
+
*
|
|
35
|
+
* 背景:旧实现会遍历 node_modules,把 `mysql2/index.js` 与 `mysql2/promise.js`
|
|
36
|
+
* 的 require.cache 条目替换为内存兼容层。由于它靠 `process.cwd()` 猜测路径,
|
|
37
|
+
* 命中哪个 node_modules 完全取决于启动目录,且 mysql2 通常尚未加载,
|
|
38
|
+
* 于是被静默替换 —— 真实 MySQL 连接悄无声息地改查内存库且不报错,
|
|
39
|
+
* 表现为「本地跑通、上线全错」。这是不可接受的隐式副作用。
|
|
40
|
+
*
|
|
41
|
+
* 现在请显式选择注入方式:
|
|
42
|
+
*
|
|
43
|
+
* 1) 直接使用兼容层(推荐,零副作用):
|
|
44
|
+
* const { mysql2 } = require('jsql-neo');
|
|
45
|
+
* const conn = await mysql2.createConnection(opts);
|
|
46
|
+
*
|
|
47
|
+
* 2) 让某个依赖改用兼容层(只对指定模块生效,不污染全局缓存):
|
|
48
|
+
* const { injectMySQLCompat } = require('jsql-neo');
|
|
49
|
+
* injectMySQLCompat(require('typeorm')); // 只改写 typeorm 自己的引用
|
|
50
|
+
*
|
|
51
|
+
* 3) 确实需要全局劫持(危险,仅限测试环境):
|
|
52
|
+
* enableMySQLCompat({ global: true, force: true });
|
|
53
|
+
* 需同时设置 JSQL_NEO_ALLOW_GLOBAL_HIJACK=1 作为显式确认。
|
|
54
|
+
*
|
|
55
|
+
* @param {object} [opts]
|
|
56
|
+
* @param {boolean} [opts.global=false] 是否允许改写 require.cache(需环境变量确认)
|
|
57
|
+
* @param {boolean} [opts.force=false] 是否覆盖已加载的真实 mysql2
|
|
58
|
+
* @returns {object} mysql2 兼容层
|
|
35
59
|
*/
|
|
36
|
-
function enableMySQLCompat() {
|
|
60
|
+
function enableMySQLCompat(opts = {}) {
|
|
61
|
+
if (!opts || opts.global !== true) {
|
|
62
|
+
// 默认安全:什么都不做,仅返回兼容层供显式使用
|
|
63
|
+
return mysqlCompat;
|
|
64
|
+
}
|
|
65
|
+
if (process.env.JSQL_NEO_ALLOW_GLOBAL_HIJACK !== '1') {
|
|
66
|
+
throw new Error(
|
|
67
|
+
'[jsql-neo] 已拒绝全局劫持 require.cache:这会静默替换真实 mysql2,' +
|
|
68
|
+
'导致本地测试通过但生产连错库。\n' +
|
|
69
|
+
'如确需启用,请显式设置环境变量 JSQL_NEO_ALLOW_GLOBAL_HIJACK=1 并传 { global: true }。\n' +
|
|
70
|
+
'推荐改用显式注入:injectMySQLCompat(require(\'你的orm\')),或直接使用导出的 mysql2/createConnection。'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
37
73
|
const fs = require('fs');
|
|
38
74
|
const path = require('path');
|
|
39
75
|
const seen = new Set();
|
|
@@ -51,8 +87,7 @@ function enableMySQLCompat() {
|
|
|
51
87
|
if (seen.has(resolved)) return;
|
|
52
88
|
if (!fs.existsSync(resolved)) return;
|
|
53
89
|
seen.add(resolved);
|
|
54
|
-
|
|
55
|
-
if (require.cache[resolved]) return;
|
|
90
|
+
if (require.cache[resolved] && !opts.force) return;
|
|
56
91
|
require.cache[resolved] = { exports: mod, id: resolved, filename: resolved, loaded: true, children: [] };
|
|
57
92
|
} catch (e) { /* ignore */ }
|
|
58
93
|
};
|
|
@@ -63,6 +98,37 @@ function enableMySQLCompat() {
|
|
|
63
98
|
return mysqlCompat;
|
|
64
99
|
}
|
|
65
100
|
|
|
101
|
+
/**
|
|
102
|
+
* 把已加载模块自身持有的 `mysql2` 引用定向到 jsql-neo 兼容层。
|
|
103
|
+
*
|
|
104
|
+
* 与全局劫持不同,本函数**只修改传入模块对象上的引用**,
|
|
105
|
+
* 不触碰 require.cache,不影响进程内其他模块。
|
|
106
|
+
*
|
|
107
|
+
* @param {object} mod 目标模块的 exports(如 require('typeorm'))
|
|
108
|
+
* @returns {boolean} 是否成功改写
|
|
109
|
+
*/
|
|
110
|
+
function injectMySQLCompat(mod) {
|
|
111
|
+
if (!mod || typeof mod !== 'object') return false;
|
|
112
|
+
let touched = false;
|
|
113
|
+
// 常见形态:模块把 createConnection / createPool 挂在自身或嵌套对象上
|
|
114
|
+
const patch = (target) => {
|
|
115
|
+
if (!target || typeof target !== 'object') return;
|
|
116
|
+
for (const key of ['createConnection', 'createPool']) {
|
|
117
|
+
if (typeof target[key] === 'function' && target[key] !== mysqlCompat[key]) {
|
|
118
|
+
target[key] = mysqlCompat[key];
|
|
119
|
+
touched = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
patch(mod);
|
|
124
|
+
// 一层嵌套(drizzle / kysely 等常把驱动挂在子对象上)
|
|
125
|
+
for (const key of Object.keys(mod)) {
|
|
126
|
+
const v = mod[key];
|
|
127
|
+
if (v && typeof v === 'object' && v !== mod && !Array.isArray(v)) patch(v);
|
|
128
|
+
}
|
|
129
|
+
return touched;
|
|
130
|
+
}
|
|
131
|
+
|
|
66
132
|
module.exports = {
|
|
67
133
|
JSQL: WasmClient.JSQL,
|
|
68
134
|
NativeJSQL: NativeClient.JSQL,
|
|
@@ -96,6 +162,7 @@ module.exports = {
|
|
|
96
162
|
mysql: mysqlCompat,
|
|
97
163
|
mysql2: mysqlCompat,
|
|
98
164
|
enableMySQLCompat,
|
|
165
|
+
injectMySQLCompat,
|
|
99
166
|
createMysqlServer,
|
|
100
167
|
MysqlServer,
|
|
101
168
|
// 迁移工具: mysqldump 导入 / JSON / CSV
|
package/lib/btree.js
CHANGED
|
@@ -433,48 +433,12 @@ class BTree {
|
|
|
433
433
|
/**
|
|
434
434
|
* 批量加载:从已排序的 [key, rowIndex] 数组底部向上构建
|
|
435
435
|
* O(n) 时间,适合大数据量批量构建
|
|
436
|
+
*
|
|
437
|
+
* @deprecated 已移除(6.3.4)。原因:本方法用 `child.keys[0]` 作为内部分隔键,
|
|
438
|
+
* 而 insert/_splitChild 路径的分隔键是「左叶最大键的副本」,两者语义不一致。
|
|
439
|
+
* 混用会导致 search/range 漏查或错查。当前索引构建统一走 insert()
|
|
440
|
+
* (见 table.js 的 rebuild 路径),本方法无任何调用方,故直接移除而非修复。
|
|
436
441
|
*/
|
|
437
|
-
bulkLoad(sortedPairs) {
|
|
438
|
-
if (sortedPairs.length === 0) { this.clear(); return; }
|
|
439
|
-
const order = this._order;
|
|
440
|
-
// 第一层:构建叶子节点
|
|
441
|
-
const leaves = [];
|
|
442
|
-
let i = 0;
|
|
443
|
-
while (i < sortedPairs.length) {
|
|
444
|
-
const node = new BTreeNode(true);
|
|
445
|
-
const end = Math.min(i + order - 1, sortedPairs.length);
|
|
446
|
-
for (; i < end; i++) {
|
|
447
|
-
const [key, val] = sortedPairs[i];
|
|
448
|
-
node.keys.push(key);
|
|
449
|
-
node.values.push(Array.isArray(val) ? val : [val]);
|
|
450
|
-
}
|
|
451
|
-
leaves.push(node);
|
|
452
|
-
}
|
|
453
|
-
for (let i = 0; i < leaves.length - 1; i++) leaves[i].next = leaves[i + 1];
|
|
454
|
-
if (leaves.length === 1) { this._root = leaves[0]; this._size = sortedPairs.length; return; }
|
|
455
|
-
// 逐层向上构建内部节点
|
|
456
|
-
let currentLevel = leaves;
|
|
457
|
-
while (currentLevel.length > 1) {
|
|
458
|
-
const parents = [];
|
|
459
|
-
let j = 0;
|
|
460
|
-
while (j < currentLevel.length) {
|
|
461
|
-
const node = new BTreeNode(false);
|
|
462
|
-
const end = Math.min(j + order, currentLevel.length);
|
|
463
|
-
for (; j < end; j++) {
|
|
464
|
-
if (node.children.length > 0) {
|
|
465
|
-
const child = currentLevel[j];
|
|
466
|
-
node.keys.push(child.keys[0]);
|
|
467
|
-
node.values.push(child.values[0]);
|
|
468
|
-
}
|
|
469
|
-
node.children.push(currentLevel[j]);
|
|
470
|
-
}
|
|
471
|
-
parents.push(node);
|
|
472
|
-
}
|
|
473
|
-
currentLevel = parents;
|
|
474
|
-
}
|
|
475
|
-
this._root = currentLevel[0];
|
|
476
|
-
this._size = sortedPairs.length;
|
|
477
|
-
}
|
|
478
442
|
}
|
|
479
443
|
|
|
480
444
|
module.exports = BTree;
|