nodejs-store 1.0.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.
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 读路径 —— find 快路径 / 两阶段(取 ID → 关联 → 还原排序)/ 标准聚合 + asyncFn 尾处理
5
+ * / 跨库联邦(逐源执行 → 内存 hash join)
6
+ */
7
+
8
+ const { core: _core, getAsyncFn } = require('../schema');
9
+ const { _call, _ctx, _exec, _execOn, resolvePlaceholders } = require('./exec');
10
+
11
+ /** 执行读命令序列:find 快路径 / 两阶段(取 ID → 关联 → 还原排序)/ 标准聚合 */
12
+ async function _runQueryPlan(plan) {
13
+ if (plan.mode === 'two_phase') {
14
+ const idDocs = await _exec(plan.commands[0]);
15
+ const ids = idDocs.map((d) => d._id);
16
+ if (!ids.length) return [];
17
+ const cmd2 = resolvePlaceholders(plan.commands[1], { ids });
18
+ const items = await _exec(cmd2);
19
+ return _core.restoreSortOrder(items, ids, plan.sort ?? null).items;
20
+ }
21
+ return _exec(plan.commands[0]);
22
+ }
23
+
24
+ /** 读路径尾处理两段式:core 后处理 → Host 执行 asyncFn → core 剥离注入依赖 */
25
+ async function _finalize(plan, items) {
26
+ if (!plan.postprocess) return items;
27
+ const prepared = _core.prepareQuery(plan.postprocess, items, _ctx());
28
+ for (const ref of prepared.fnRefs) {
29
+ const fn = getAsyncFn(ref);
30
+ if (!fn) throw new Error(`asyncFn 计算列 ${ref} 未注册实现`);
31
+ await fn(prepared.items, _ctx());
32
+ }
33
+ return _core.stripQuery(plan.postprocess, prepared.items).items;
34
+ }
35
+
36
+ /**
37
+ * GQL 查询(返回数组)
38
+ *
39
+ * 支持的 params 键(通过 GQL 的 @key 引用):
40
+ * $condition / $sort / $skip / $limit / $pipeline
41
+ * 使用 $pipeline 时,框架不追加 compute 层、不补默认值、不裁剪,完全由用户控制。
42
+ *
43
+ * `routeOverride`(多租户路由,可选):`{ source?, namespace? }`,覆盖命令定位,
44
+ * 权限/计算列仍按结构 schema 判定(见 multi-datasource-routing-plan.md §6)。
45
+ */
46
+ async function query(gql, params = null, routeOverride = null) {
47
+ const plan = _call(() => _core.planQuery(gql, params ?? {}, _ctx(), routeOverride));
48
+ return _finalize(plan, await _runQueryPlan(plan));
49
+ }
50
+
51
+ /** GQL 查询(返回单条) */
52
+ async function queryOne(gql, params = null, routeOverride = null) {
53
+ const items = await query(gql, params, routeOverride);
54
+ return items.length ? items[0] : null;
55
+ }
56
+
57
+ /**
58
+ * 执行单个联邦取数单元(按 `sources[].source` 精确路由;two_phase 走两阶段)
59
+ *
60
+ * 与单库 `_runQueryPlan` 同形:只差路由键(单元自带 source,不按 collection 反查)。
61
+ */
62
+ async function _runFederatedUnit(unit) {
63
+ const commands = unit.commands || [];
64
+ if (unit.mode === 'two_phase') {
65
+ const idDocs = await _execOn(unit.source, commands[0]);
66
+ const ids = idDocs.map((d) => d._id);
67
+ if (!ids.length) return [];
68
+ const cmd2 = resolvePlaceholders(commands[1], { ids });
69
+ const items = await _execOn(unit.source, cmd2);
70
+ return _core.restoreSortOrder(items, ids, unit.sort ?? null).items;
71
+ }
72
+ return _execOn(unit.source, commands[0]);
73
+ }
74
+
75
+ /**
76
+ * 跨库联邦查询(返回嵌套文档数组)
77
+ *
78
+ * Host 四步:core `planFederated` 拆源 → 逐源执行 → core `mergeFederated`
79
+ * 内存 hash join → 统一后处理(`_finalize`,与单库同一路径)。
80
+ *
81
+ * `postprocess` 取自根单元快照(含全部关系),因此结果形状与单库 `query` 完全一致。
82
+ * 每源取数上限 `MAX_FEDERATION_ROWS` 由 core 强制(超限即报错,拒绝静默全表拉取);
83
+ * 无法下推的分页/排序进 `plan.degraded` 并告警,不阻断查询。
84
+ */
85
+ async function queryFederated(gql, params = null) {
86
+ const plan = _call(() => _core.planFederated(gql, params ?? {}, _ctx()));
87
+
88
+ for (const d of plan.degraded || []) {
89
+ console.warn(`[federation] 降级 ${(d && d.code) || ''}: ${(d && d.message) || ''}`);
90
+ }
91
+
92
+ const results = [];
93
+ for (const unit of plan.sources || []) {
94
+ results.push(await _runFederatedUnit(unit));
95
+ }
96
+
97
+ const merged = _call(() => _core.mergeFederated(plan, results));
98
+ return _finalize(plan, merged);
99
+ }
100
+
101
+ /**
102
+ * GQL 查询(返回 items + total + 分页元数据)
103
+ *
104
+ * 支持两种分页参数方式:
105
+ * 1. page/pageSize(推荐)— 自动计算 skip/limit,page 默认 0,pageSize 默认 50
106
+ * 2. 传统 $skip/$limit — 从 GQL 参数推导 page/pageSize
107
+ * pageSize 上限 5000,防止拖库。
108
+ */
109
+ async function queryWithCount(gql, params = null, routeOverride = null) {
110
+ const plan = _call(() =>
111
+ _core.planQueryWithCount(gql, params ?? {}, _ctx(), null, routeOverride));
112
+ const items = await _finalize(plan, await _runQueryPlan(plan));
113
+ const total = await _exec(plan.countCommand);
114
+ return {
115
+ items,
116
+ total,
117
+ hasMore: (plan.page + 1) * plan.pageSize < total,
118
+ page: plan.page,
119
+ pageSize: plan.pageSize,
120
+ };
121
+ }
122
+
123
+ module.exports = { query, queryOne, queryWithCount, queryFederated };
@@ -0,0 +1,103 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 写路径 —— 单条/批量插入、更新、删除归档、存在性与计数
5
+ */
6
+
7
+ const { core: _core, get: _getSchema } = require('../schema');
8
+ const { _call, _ctx, _exec, _now } = require('./exec');
9
+ const { _generateId } = require('./id');
10
+
11
+ /** creator 写权限探针:先规划,若 needsProbe 则执行探针命令后重入 */
12
+ async function _planWithProbe(planFn) {
13
+ let out = planFn(null, null);
14
+ if (out.needsProbe) {
15
+ const probeDoc = await _exec(out.needsProbe);
16
+ out = planFn(probeDoc !== null && probeDoc !== undefined, probeDoc ?? null);
17
+ }
18
+ return out;
19
+ }
20
+
21
+ /** 插入一条(`routeOverride` 可选:`{ source?, namespace? }` 多租户路由) */
22
+ async function insert(schemaName, data, routeOverride = null) {
23
+ const s = _getSchema(schemaName);
24
+ const plan = _call(() =>
25
+ _core.planInsert(schemaName, data ?? null, _now(), s.idPrefix ? _generateId(s) : '', _ctx(),
26
+ routeOverride));
27
+ await _exec(plan.command);
28
+ return plan.returns;
29
+ }
30
+
31
+ /** 批量插入(带权限检查,自动生成 _id 和时间戳;空数组直接返回空) */
32
+ async function insertMany(schemaName, docs, routeOverride = null) {
33
+ if (!Array.isArray(docs) || !docs.length) return [];
34
+
35
+ const s = _getSchema(schemaName);
36
+ const plan = _call(() => _core.planInsertMany(
37
+ schemaName,
38
+ docs,
39
+ _now(),
40
+ // core 按需消费(仅无 _id 的文档取用),多备无害
41
+ docs.map(() => (s.idPrefix ? _generateId(s) : '')),
42
+ _ctx(),
43
+ routeOverride,
44
+ ));
45
+ if (plan.command) await _exec(plan.command);
46
+ return plan.returns;
47
+ }
48
+
49
+ /**
50
+ * 更新一条(支持原生操作符,不触发默认值)
51
+ *
52
+ * data 的 key 以 '$' 开头 → 原生 MongoDB 操作符($set/$inc/$unset 等)直接透传。
53
+ * 否则自动包装为 $set 模式。`routeOverride` 可选(多租户路由)。
54
+ */
55
+ async function update(schemaName, condition, data, options = null, routeOverride = null) {
56
+ const out = await _planWithProbe((found, doc) => _call(() =>
57
+ _core.planUpdate(schemaName, condition ?? null, data ?? null, options ?? null, _now(), _ctx(),
58
+ found, doc, routeOverride)));
59
+ const result = await _exec(out.command);
60
+ return result ? _call(() => _core.applyWriteDefaults(schemaName, result)) : null;
61
+ }
62
+
63
+ /** 批量更新(支持原生操作符) */
64
+ async function updateMany(schemaName, condition, data, routeOverride = null) {
65
+ const out = _call(() =>
66
+ _core.planUpdateMany(schemaName, condition ?? null, data ?? null, _now(), _ctx(), routeOverride));
67
+ const result = await _exec(out.command);
68
+ return { modifiedCount: result.modifiedCount };
69
+ }
70
+
71
+ /** 删除 —— 原表数据先归档到对应 `_deleted` 附表(附 deletedAt),再物理删除原表数据 */
72
+ async function remove(schemaName, condition, routeOverride = null) {
73
+ const out = await _planWithProbe((found, doc) => _call(() =>
74
+ _core.planRemove(schemaName, condition ?? null, _ctx(), found, doc, routeOverride)));
75
+
76
+ let archivedCount = 0;
77
+ if (out.findCommand) {
78
+ const docs = await _exec(out.findCommand);
79
+ if (docs.length) {
80
+ const arch = _call(() => _core.planArchiveDocs(schemaName, docs, _now(), routeOverride));
81
+ await _exec(arch.command);
82
+ archivedCount = docs.length;
83
+ }
84
+ }
85
+
86
+ const result = await _exec(out.deleteCommand);
87
+ return { deletedCount: result.deletedCount, archivedCount };
88
+ }
89
+
90
+ /** 判断是否存在 */
91
+ async function exists(schemaName, condition, routeOverride = null) {
92
+ const cmd = _call(() => _core.planExists(schemaName, condition ?? null, routeOverride));
93
+ const doc = await _exec(cmd);
94
+ return doc !== null && doc !== undefined;
95
+ }
96
+
97
+ /** 统计符合条件的文档数量 */
98
+ async function count(schemaName, filter = null, routeOverride = null) {
99
+ const cmd = _call(() => _core.planCount(schemaName, filter ?? null, routeOverride));
100
+ return _exec(cmd);
101
+ }
102
+
103
+ module.exports = { insert, insertMany, update, updateMany, remove, exists, count };
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 数据源路由(多后端)
5
+ *
6
+ * core 产出的 Command 携带 `source` / `namespace` / `collection` 三元组
7
+ * (见 rust-store/core 的 Command 契约),Host 只按 `source` 选连接、
8
+ * 按 `namespace` 定位连接内的库/schema:
9
+ * - Mongo 源:直接交原生驱动(`db.collection(...)`)
10
+ * - SQL 源(mysql / postgres / sqlite):先经 core `dialectTranslate` 翻译为
11
+ * SQL 语句序列,再交该连接的 `exec` 执行器
12
+ *
13
+ * Mongo 连接支持两种形态(绝不猜,按命令的 namespace 严格校验):
14
+ * - db 实例:命令 `namespace` 必须为 null(db 实例无法跨库,非 null 显式报错)
15
+ * - MongoClient:命令 `namespace` 必须非 null(db 名)→ `client.db(ns).collection(...)`
16
+ *
17
+ * 数据源名缺省为 `default`;`init` 传入单个 Mongo db 实例时自动归一为
18
+ * `{ default: db }`,保证既有单库调用零变更。
19
+ */
20
+
21
+ const { core: _core, get: _getSchema } = require('./schema');
22
+ const executors = require('./executors');
23
+
24
+ const DEFAULT_SOURCE = 'default';
25
+
26
+ let _connections = Object.create(null);
27
+
28
+ /** Mongo 形态判别:db 实例(collection 为函数)或 MongoClient(db 为函数且无 collection) */
29
+ function _isMongoHandle(x) {
30
+ return (
31
+ !!x &&
32
+ (typeof x.collection === 'function' ||
33
+ (typeof x.db === 'function' && typeof x.collection !== 'function'))
34
+ );
35
+ }
36
+
37
+ /** 归一化连接映射:单个 Mongo db 实例 / MongoClient → `{ default: 连接 }` */
38
+ function _normalize(connections) {
39
+ if (_isMongoHandle(connections)) {
40
+ return { [DEFAULT_SOURCE]: connections };
41
+ }
42
+ return connections || {};
43
+ }
44
+
45
+ /** 设置数据源连接映射(Mongo 传 db 实例或 MongoClient;SQL 传 `{ kind, exec }` 描述符) */
46
+ function setConnections(connections) {
47
+ _connections = Object.assign(Object.create(null), _normalize(connections));
48
+ }
49
+
50
+ /** 取指定数据源连接(未配置即报错) */
51
+ function getConnection(source) {
52
+ const conn = _connections[source];
53
+ if (conn === undefined) {
54
+ throw new Error(
55
+ `数据源未配置: ${source}(请检查 init(connections) 与 schema 的 datasource 绑定)`,
56
+ );
57
+ }
58
+ return conn;
59
+ }
60
+
61
+ /**
62
+ * Mongo 源:按命令的 `namespace` 解析目标 db(两种形态,绝不猜)
63
+ *
64
+ * - db 实例(`db.collection` 为函数):namespace 必须为 null,非 null 显式报错;
65
+ * - MongoClient(`db` 为函数且无 `collection`):namespace 必须非 null,
66
+ * 返回 `client.db(namespace)`;
67
+ * - 非 Mongo(SQL 描述符)返回 null,由调用方走 SQL 路径。
68
+ */
69
+ function mongoDb(connection, source, namespace) {
70
+ if (typeof connection.collection === 'function') {
71
+ if (namespace) {
72
+ throw new Error(
73
+ `数据源 ${source} 是 Mongo db 实例,命令携带了 namespace="${namespace}"(db 实例不支持跨库;跨库请改传 MongoClient 并用 schema.namespace 声明库名)`,
74
+ );
75
+ }
76
+ return connection;
77
+ }
78
+ if (typeof connection.db === 'function') {
79
+ if (!namespace) {
80
+ throw new Error(
81
+ `数据源 ${source} 是 MongoClient,命令缺少 namespace( MongoClient 形态必须在 schema 声明 namespace 即 db 名)`,
82
+ );
83
+ }
84
+ return connection.db(namespace);
85
+ }
86
+ return null;
87
+ }
88
+
89
+ /** schema 声明的数据源名(缺省 `default`) */
90
+ function sourceOfSchema(name) {
91
+ return _getSchema(name).datasource || DEFAULT_SOURCE;
92
+ }
93
+
94
+ /** 某 schema 所属数据源的连接(供 Host 侧直连场景) */
95
+ function connectionOfSchema(name) {
96
+ return getConnection(sourceOfSchema(name));
97
+ }
98
+
99
+ /** 某 schema 的 Mongo db 句柄(按镜像的 datasource + namespace 解析;SQL 源返回 null) */
100
+ function dbOfSchema(name) {
101
+ const s = _getSchema(name);
102
+ const source = s.datasource || DEFAULT_SOURCE;
103
+ return mongoDb(getConnection(source), source, s.namespace || null);
104
+ }
105
+
106
+ /** Command.source → `{ source, connection }`(三元组中的 source 精确路由) */
107
+ function route(cmd) {
108
+ const source = cmd.source || DEFAULT_SOURCE;
109
+ return { source, connection: getConnection(source) };
110
+ }
111
+
112
+ /**
113
+ * SQL 路径:translate(core 纯逻辑)→ exec(连接执行器)→ 结果塑形
114
+ *
115
+ * 执行器只做「绑定参数 + 执行 + restoreRows」,返回中立包络;此处依 command.kind
116
+ * 塑形为 Mongo 驱动等价返回值(见 `./executors/index.js#shapeResult`),
117
+ * 使上层(crud/*)对 Mongo / SQL 两条路径无感。
118
+ */
119
+ async function execSql(source, connection, cmd) {
120
+ if (typeof connection.exec !== 'function') {
121
+ throw new Error(
122
+ `SQL 数据源 ${source}(${connection.kind}) 的执行器未接入(见执行文档 Phase 4)`,
123
+ );
124
+ }
125
+ const plan = _core.dialectTranslate(connection.kind, cmd);
126
+ // Host 兜底:core 标记了无法安全下推的组合(如 $lookup 子 $limit 每父 top-N)时,
127
+ // 绝不执行「缺少该段」的 SQL(会静默返回错误结果),改为显式报错,由调用方降级重查。
128
+ if (Array.isArray(plan.unsupported) && plan.unsupported.length > 0) {
129
+ const codes = plan.unsupported.map((u) => (u && u.code) || String(u)).join(', ');
130
+ throw new Error(
131
+ `SQL 下推不支持(${connection.kind}): ${codes};${(plan.warnings || []).join(' / ')}`,
132
+ );
133
+ }
134
+ const out = await connection.exec(plan);
135
+ return executors.shapeResult(cmd, out);
136
+ }
137
+
138
+ module.exports = {
139
+ DEFAULT_SOURCE,
140
+ setConnections,
141
+ getConnection,
142
+ mongoDb,
143
+ sourceOfSchema,
144
+ connectionOfSchema,
145
+ dbOfSchema,
146
+ route,
147
+ execSql,
148
+ };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SQL 执行器注册与结果塑形(Phase 4)
5
+ *
6
+ * 分层(对齐铁律 1/8):
7
+ * core `dialectTranslate`(纯逻辑,产 SQL + rowShape)
8
+ * → 执行器 `{mysql,postgres,sqlite}`(绑定参数 + 执行 + `restoreRows`)
9
+ * → 本模块把中立包络 `{docs, rows, affectedRows}` 塑形为 **Mongo 驱动等价返回值**
10
+ *
11
+ * 塑形规则与 `crud/exec.js#_execMongo` 逐一对应,保证 Mongo / SQL 两条路径对上层
12
+ * (`crud/query|write|mutation`)透明:上层只看到同一套返回值语义。
13
+ */
14
+
15
+ const mysql = require('./mysql');
16
+ const postgres = require('./postgres');
17
+ const sqlite = require('./sqlite');
18
+
19
+ const _BACKENDS = { mysql, postgres, sqlite };
20
+
21
+ /** 创建 SQL 数据源连接描述符 `{ kind, exec }`(driver 为对应驱动实例/连接) */
22
+ function createConnection(kind, driver, options) {
23
+ const mod = _BACKENDS[kind];
24
+ if (!mod) throw new Error(`未知 SQL 后端: ${kind}(支持 mysql/postgres/sqlite)`);
25
+ return mod.create(driver, options);
26
+ }
27
+
28
+ /** 取行首列标量(COUNT 等聚合列无稳定别名,取首个值;PG 的 bigint 为字符串需数值化) */
29
+ function _scalar(rows) {
30
+ const row = rows && rows[0];
31
+ if (!row) return 0;
32
+ const v = Object.values(row)[0];
33
+ return typeof v === 'string' ? Number(v) : v;
34
+ }
35
+
36
+ /** 中立包络 → Mongo 驱动等价返回值 */
37
+ function shapeResult(cmd, out) {
38
+ switch (cmd.kind) {
39
+ case 'find':
40
+ case 'aggregate':
41
+ return out.docs || [];
42
+ case 'findOne':
43
+ case 'findOneAndUpdate':
44
+ return (out.docs && out.docs[0]) || null;
45
+ case 'countDocuments':
46
+ return _scalar(out.rows);
47
+ case 'insertOne':
48
+ return cmd.doc;
49
+ case 'insertMany':
50
+ return { insertedCount: (cmd.docs || []).length };
51
+ case 'updateMany':
52
+ return { modifiedCount: out.affectedRows };
53
+ case 'deleteMany':
54
+ return { deletedCount: out.affectedRows };
55
+ default:
56
+ return out;
57
+ }
58
+ }
59
+
60
+ module.exports = { createConnection, shapeResult, mysql, postgres, sqlite };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * MySQL 执行器(驱动:mysql2/promise)
5
+ *
6
+ * 只做「绑定参数 + 执行 + 回喂」(铁律 1/8):SQL 全部由 core `dialectTranslate`
7
+ * 产出(占位符为 `?`),本模块不拼任何 SQL。MySQL 无 `RETURNING`,写后回读由 core
8
+ * 产出「UPDATE/INSERT + SELECT」两条语句,本模块按序执行并取回读结果即可。
9
+ * 返回中立包络 `{ docs, rows, affectedRows }`,由 `./index.js` 依 command.kind 塑形。
10
+ */
11
+
12
+ const { core: _core } = require('../schema');
13
+
14
+ /** mysql2 返回 RowDataPacket 实例,转普通对象后再交 core(绑定层只认纯 JSON) */
15
+ function _plain(row) {
16
+ const out = {};
17
+ for (const k of Object.keys(row)) out[k] = row[k];
18
+ return out;
19
+ }
20
+
21
+ /** 创建执行器描述符(可直接作为 `init(connections)` 的一个 SQL 数据源连接) */
22
+ function create(driver, _options = {}) {
23
+ if (!driver || typeof driver.execute !== 'function') {
24
+ throw new TypeError('mysql 执行器需要 mysql2/promise 的连接或连接池');
25
+ }
26
+ return {
27
+ kind: 'mysql',
28
+ async exec(plan) {
29
+ let docs = null;
30
+ let rows = null;
31
+ let affectedRows = 0;
32
+ for (const stmt of plan.stmts) {
33
+ const [raw] = await driver.execute(stmt.text, stmt.params || []);
34
+ if (Array.isArray(raw)) {
35
+ rows = raw.map(_plain);
36
+ if (stmt.rowShape) docs = _core.restoreRows(stmt.rowShape, rows);
37
+ } else {
38
+ affectedRows = Number(raw.affectedRows || 0);
39
+ }
40
+ }
41
+ return { docs, rows, affectedRows };
42
+ },
43
+ };
44
+ }
45
+
46
+ module.exports = { create };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PostgreSQL 执行器(驱动:pg)
5
+ *
6
+ * 只做「绑定参数 + 执行 + 回喂」(铁律 1/8):SQL 全部由 core `dialectTranslate`
7
+ * 产出(占位符为 `$n`,params 顺序一致),本模块不拼任何 SQL。PG 原生支持
8
+ * `RETURNING`,故写后回读为单语句。返回中立包络 `{ docs, rows, affectedRows }`,
9
+ * 由 `./index.js` 依 command.kind 塑形为 Mongo 驱动等价返回值。
10
+ */
11
+
12
+ const { core: _core } = require('../schema');
13
+
14
+ /** 创建执行器描述符(可直接作为 `init(connections)` 的一个 SQL 数据源连接) */
15
+ function create(driver, _options = {}) {
16
+ if (!driver || typeof driver.query !== 'function') {
17
+ throw new TypeError('postgres 执行器需要 pg 的 Pool/Client 实例');
18
+ }
19
+ return {
20
+ kind: 'postgres',
21
+ async exec(plan) {
22
+ let docs = null;
23
+ let rows = null;
24
+ let affectedRows = 0;
25
+ for (const stmt of plan.stmts) {
26
+ const res = await driver.query(stmt.text, stmt.params || []);
27
+ rows = res.rows || [];
28
+ affectedRows = Number(res.rowCount || 0);
29
+ if (stmt.rowShape) docs = _core.restoreRows(stmt.rowShape, rows);
30
+ }
31
+ return { docs, rows, affectedRows };
32
+ },
33
+ };
34
+ }
35
+
36
+ module.exports = { create };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SQLite 执行器(驱动:better-sqlite3)
5
+ *
6
+ * 只做「绑定参数 + 执行 + 回喂」(铁律 1/8):SQL 全部由 core `dialectTranslate`
7
+ * 产出,本模块不拼任何 SQL;带 `rowShape` 的语句结果交 core `restoreRows` 还原为
8
+ * 嵌套文档。返回值是中立包络 `{ docs, rows, affectedRows }`,由 `./index.js`
9
+ * 依 command.kind 塑形为 Mongo 驱动等价返回值。
10
+ */
11
+
12
+ const { core: _core } = require('../schema');
13
+
14
+ /** better-sqlite3 只接受 number/string/bigint/Buffer/null,布尔需显式转 0/1 */
15
+ function _bind(params) {
16
+ return (params || []).map((v) => {
17
+ if (v === true) return 1;
18
+ if (v === false) return 0;
19
+ return v === undefined ? null : v;
20
+ });
21
+ }
22
+
23
+ /** 创建执行器描述符(可直接作为 `init(connections)` 的一个 SQL 数据源连接) */
24
+ function create(db, _options = {}) {
25
+ if (!db || typeof db.prepare !== 'function') {
26
+ throw new TypeError('sqlite 执行器需要 better-sqlite3 Database 实例');
27
+ }
28
+ return {
29
+ kind: 'sqlite',
30
+ exec(plan) {
31
+ let docs = null;
32
+ let rows = null;
33
+ let affectedRows = 0;
34
+ for (const stmt of plan.stmts) {
35
+ const params = _bind(stmt.params);
36
+ // 带 RETURNING 的写语句同样返回行 → 必须用 all() 取回;其余写语句用 run() 取影响行数
37
+ if (!stmt.isWrite || stmt.rowShape) {
38
+ rows = db.prepare(stmt.text).all(...params);
39
+ if (stmt.rowShape) docs = _core.restoreRows(stmt.rowShape, rows);
40
+ } else {
41
+ affectedRows = Number(db.prepare(stmt.text).run(...params).changes || 0);
42
+ }
43
+ }
44
+ return { docs, rows, affectedRows };
45
+ },
46
+ };
47
+ }
48
+
49
+ module.exports = { create };