dsh-db-tool 1.3.1 → 1.3.2

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 CHANGED
@@ -36,7 +36,7 @@ dsh plugin --profile web add "dsh-db-tool@github:mengqi1436/dsh-db-tool"
36
36
  dsh plugin --profile web add "link:E:\path\to\dsh-db-tool"
37
37
  ```
38
38
 
39
- GaussDB 官方驱动未发布 npm,需先构建 vendor:`npm run build:gaussdb`(PowerShell)或 `bash scripts/build-gaussdb.sh`;运行要求 Node ≥ 22.12(产物依赖 p-limit@7 纯 ESM)。oracledb 安装脚本需 `npm approve-scripts oracledb`。mongodb 驱动已 bundle 进发布包(`vendor/mongodb-driver.cjs`,esbuild 构建,`npm run build:mongodb` 可重建),依赖树中不含 mongodb/punycode,详见 [docs/install.md](docs/install.md)。
39
+ GaussDB 官方驱动未发布 npm,需先构建 vendor:`npm run build:gaussdb`(PowerShell)或 `bash scripts/build-gaussdb.sh`;运行要求 Node ≥ 22.12(产物依赖 p-limit@7 纯 ESM)。oracledb 安装脚本需 `npm approve-scripts oracledb`。mongodb 驱动已 bundle 进发布包(`vendor/mongodb-driver.cjs`,esbuild 构建,`npm run build:mongodb` 可重建),依赖树中不含 mongodb/punycode(守卫见「Troubleshooting」免补丁路径说明)。
40
40
 
41
41
  ### 桌面端安装
42
42
 
@@ -58,6 +58,26 @@ DSH 桌面端 0.2.0-rc.2+ 捆绑了 `dsh` 命令,全程无需另装 Node 或 p
58
58
  - **GaussDB**:npm 发布包不含 GaussDB vendor 驱动。需要 GaussDB 的用户请改从源码安装(`dsh plugin --profile desktop add "dsh-db-tool@github:mengqi1436/dsh-db-tool"`),并在插件目录自行 `npm run build:gaussdb`(要求 Node ≥ 22.12)。
59
59
  - **Oracle**:安装后需 `npm approve-scripts oracledb` 放行 oracledb 安装脚本(或按 DSH 插件安装界面的脚本审批提示放行)。
60
60
 
61
+ ### 离线环境安装
62
+
63
+ **症状(≤ 1.3.1)**:受限代理/无公网 + 无编译工具链的机器上,安装卡死在 `better-sqlite3`(原生 C++ 模块)后整体回滚(`ERR_PNPM_EXECOR_LIFECYCLE_SCRIPT_FAILED`)——官方未发布 Node 24(ABI v137)的 Windows 预构建(release 下载 404),本地编译又同时缺 Node 头文件(nodejs.org 经代理 TLS 异常)与 MSVC 工具链。
64
+
65
+ **1.3.2 起的行为**:
66
+
67
+ 1. `better-sqlite3` 移入 `optionalDependencies`——构建失败时 pnpm 自动排除该包,**插件整体安装不再回滚**(pnpm 11.7 实测:`is an optional dependency and failed … Excluding it from installation`,exit 0)。
68
+ 2. SQLite 改走**双驱动**:优先 `node:sqlite`(Node ≥ 22.5 内置,零原生依赖、离线环境直接可用;DSH 桌面端 runtime Node 24.x 自带),降级 `better-sqlite3`(存在则自动使用)。两者皆无时给出明确指引,**其余 7 种数据库不受任何影响**(MySQL/PG/GaussDB/Redis/Mongo/Oracle/DM 均为纯 JS 驱动)。
69
+ 3. 可用环境变量 `DBT_SQLITE_DRIVER=node|better` 强制单一路径(默认自动选择)。
70
+
71
+ SQLite 支持矩阵:
72
+
73
+ | 环境 | SQLite 可用性 |
74
+ |---|---|
75
+ | Node ≥ 22.5(含 DSH 桌面端 runtime 24.x) | ✅ `node:sqlite` 内置,零依赖 |
76
+ | Node < 22.5 且 better-sqlite3 可安装/编译 | ✅ 自动用 `better-sqlite3` |
77
+ | Node < 22.5 且离线(better-sqlite3 构建失败被排除) | ❌ SQLite 不可用,其余 7 库正常 |
78
+
79
+ 老版本(≤ 1.3.1)离线机的应急路径:有外网窗口时重试、装 VS Build Tools 后 `pnpm rebuild better-sqlite3`、或直接升级到 ≥ 1.3.2(推荐)。
80
+
61
81
  ## 安全模型
62
82
 
63
83
  - HTTP API 仅同源(`ctx.webServer` prefix `/dsh-db-tool/api`)+ 宿主 Connection 栅栏(`ctx.connection.requestRejection`:Host/Origin + 浏览器 token 鉴权。0.2.0-rc.1 起插件前缀路由不经宿主栅栏,故在 handler 顶层显式应用;宿主无 connection 服务时回退 loopback/Origin trust 校验,等价 ssh-tunnel `http-trust`),body 限 2MB
@@ -13,7 +13,7 @@ const INSTALL_HINTS = {
13
13
  mysql: 'npm install mysql2',
14
14
  postgresql: 'npm install pg',
15
15
  gaussdb: 'npm run build:gaussdb(构建 vendor/gaussdb-pg)',
16
- sqlite: 'npm install better-sqlite3',
16
+ sqlite: '升级 Node ≥ 22.5(内置 node:sqlite),或 npm install better-sqlite3',
17
17
  redis: 'npm install redis',
18
18
  mongodb: 'npm run build:mongodb(构建 vendor/mongodb-driver.cjs;发布包已内置产物,仅在缺失时需要)',
19
19
  oracle: 'npm install oracledb',
@@ -1,9 +1,50 @@
1
+ import { assertIdent, clampLimit, clampOffset, humanize, normalizeCell, quoteIdent } from '../sql-shared/common.js';
2
+ let cached = null;
3
+ let cachedFor = '';
1
4
  /**
2
- * SQLite 适配器(better-sqlite3,同步 API 包装 Promise)。
3
- * fields.database / fields.path 为文件路径;ro 模式 readonly + fileMustExist 打开。
5
+ * 惰性加载驱动(缓存结果);失败时抛带指引的错误。
6
+ * 可用 DBT_SQLITE_DRIVER=node|better 强制单一路径(不回退),便于测试与显式选型。
4
7
  */
5
- import Database from 'better-sqlite3';
6
- import { assertIdent, clampLimit, clampOffset, humanize, normalizeCell, quoteIdent } from '../sql-shared/common.js';
8
+ async function loadDriver() {
9
+ const forced = process.env.DBT_SQLITE_DRIVER;
10
+ if (cached && cachedFor === forced)
11
+ return cached;
12
+ const result = await (async () => {
13
+ // 1) node:sqlite:Node 22.5+ 内置(22.5–23.3 需 --experimental-sqlite)。
14
+ if (forced !== 'better') {
15
+ try {
16
+ const mod = await import('node:sqlite');
17
+ const ctor = mod.DatabaseSync;
18
+ if (typeof ctor === 'function') {
19
+ return { ctor: ctor, driver: 'node:sqlite' };
20
+ }
21
+ }
22
+ catch (e) {
23
+ if (forced === 'node')
24
+ throw new Error(`强制使用 node:sqlite 失败:${e instanceof Error ? e.message : String(e)}`);
25
+ }
26
+ }
27
+ // 2) better-sqlite3(optionalDependency,可能被离线环境排除或构建失败)。
28
+ if (forced !== 'node') {
29
+ try {
30
+ const mod = (await import('better-sqlite3'));
31
+ if (mod && typeof mod.default === 'function') {
32
+ return { ctor: mod.default, driver: 'better-sqlite3' };
33
+ }
34
+ }
35
+ catch (e) {
36
+ if (forced === 'better')
37
+ throw new Error(`强制使用 better-sqlite3 失败:${e instanceof Error ? e.message : String(e)}`);
38
+ }
39
+ }
40
+ throw new Error('SQLite 驱动不可用:当前 Node 无内置 node:sqlite(需 Node ≥ 22.5),且可选依赖 better-sqlite3 未安装或构建失败。' +
41
+ '请升级 Node 到 ≥ 22.5,或在具备编译工具链/预构建的环境执行 `npm install better-sqlite3`;' +
42
+ '其他 7 种数据库不受影响。');
43
+ })();
44
+ cached = result;
45
+ cachedFor = forced;
46
+ return result;
47
+ }
7
48
  function resolveFile(conn) {
8
49
  if (conn.fields) {
9
50
  const f = conn.fields;
@@ -26,22 +67,26 @@ function toQueryResult(raw, columns) {
26
67
  export async function createSqliteAdapter(conn, opts) {
27
68
  const file = resolveFile(conn);
28
69
  const readOnly = opts?.mode === 'ro';
70
+ const { ctor, driver } = await loadDriver();
29
71
  let db;
30
72
  try {
31
- db = readOnly
32
- ? new Database(file, { readonly: true, fileMustExist: true })
33
- : new Database(file);
73
+ // 选项名按驱动区分:node:sqlite 用 readOnly(打开不存在文件即报错);
74
+ // better-sqlite3 用 readonly + fileMustExist。node:sqlite 不接受显式
75
+ // undefined 作 options,rw 模式统一传空对象。
76
+ db = driver === 'node:sqlite'
77
+ ? new ctor(file, readOnly ? { readOnly: true } : {})
78
+ : new ctor(file, readOnly ? { readonly: true, fileMustExist: true } : {});
34
79
  }
35
80
  catch (e) {
36
81
  const msg = e instanceof Error ? e.message : String(e);
37
- throw new Error(`sqlite 打开失败 (${file}): ${msg}`);
82
+ throw new Error(`sqlite 打开失败 (${file}, 驱动 ${driver}): ${msg}`);
38
83
  }
39
84
  return {
40
85
  kind: 'sqlite',
41
86
  connId: conn.meta.id,
42
87
  testConnect: () => humanize('sqlite 连接测试', async () => {
43
88
  const row = db.prepare('SELECT sqlite_version() AS v').get();
44
- return { ok: true, serverInfo: `SQLite ${String(row?.v ?? '')}` };
89
+ return { ok: true, serverInfo: `SQLite ${String(row?.v ?? '')} (${driver})` };
45
90
  }),
46
91
  query: (sql, params) => humanize('sqlite 查询失败', async () => {
47
92
  const stmt = db.prepare(sql);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-db-tool",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -46,7 +46,6 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "better-sqlite3": "^13.0.3",
50
49
  "bson": "^7.2.0",
51
50
  "dmdb": "^1.0.52452",
52
51
  "mysql2": "^3.24.4",
@@ -54,6 +53,9 @@
54
53
  "pg": "^8.23.0",
55
54
  "redis": "^6.2.1"
56
55
  },
56
+ "optionalDependencies": {
57
+ "better-sqlite3": "^13.0.3"
58
+ },
57
59
  "devDependencies": {
58
60
  "@deepseek-ai/cordis": "^4.0.4",
59
61
  "@stryker-mutator/core": "^10.0.0",