jsql-neo 6.5.3 → 6.5.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 +1 -1
- package/bin/jsql +14 -14
- package/index.js +1 -1
- package/lib/star-nag.js +1 -1
- package/lib/wasm_client.js +38 -3
- package/native/jsql-neo-native.node +0 -0
- package/package.json +3 -3
- package/test/star-nag.test.js +6 -6
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.5.
|
|
6
|
+
> **v6.5.5** — stable release · [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/bin/jsql
CHANGED
|
@@ -201,40 +201,40 @@ const cli = yaggs({ pkg: require('../package.json') })
|
|
|
201
201
|
if (argv.reset) sn.setStarred(false);
|
|
202
202
|
const c = sn.readConfig();
|
|
203
203
|
if (c.starred) {
|
|
204
|
-
console.log(
|
|
205
|
-
console.log('Run with --reset to re-check
|
|
204
|
+
console.log(`Already confirmed starred 已确认过: ${sn.configPath()}`);
|
|
205
|
+
console.log('Run with --reset to re-check · 用 --reset 重新检查');
|
|
206
206
|
return;
|
|
207
207
|
}
|
|
208
|
-
console.log('Opening browser to confirm your star…');
|
|
208
|
+
console.log('Opening browser to confirm your star… 正在打开浏览器确认');
|
|
209
209
|
const nonceUrl = sn.startOauthCheck(); // 打开浏览器 + 后台轮询写回 ~/.jsqlmr
|
|
210
210
|
if (!process.stdout.isTTY) console.log(' ' + nonceUrl);
|
|
211
|
-
console.log(
|
|
212
|
-
console.log('Waiting for authorization (up to 3 minutes)…');
|
|
211
|
+
console.log(`Authorization updates this config 授权后写入配置: ${sn.configPath()}`);
|
|
212
|
+
console.log('Waiting for authorization (up to 3 minutes)… 等待授权(最多 3 分钟)…');
|
|
213
213
|
// startOauthCheck 的定时器是 unref 的,命令必须自己前台等,否则接不到结果
|
|
214
214
|
const res = sn.waitForResult(nonceUrl, 180000);
|
|
215
215
|
const status = res && res.status;
|
|
216
216
|
if (status === 'starred') {
|
|
217
217
|
sn.setStarred(true);
|
|
218
|
-
console.log(`Confirmed: ${res.login}
|
|
218
|
+
console.log(`Confirmed: ${res.login} starred ${res.repo} — thanks! 已确认,谢谢!`);
|
|
219
219
|
return;
|
|
220
220
|
}
|
|
221
221
|
if (status === 'not-starred') {
|
|
222
|
-
console.log(`
|
|
223
|
-
console.log(`Star it at ${sn.URL} then run \`jsql star\` again
|
|
222
|
+
console.log(`Not starred yet 还没 star: ${res.login} has not starred ${res.repo}`);
|
|
223
|
+
console.log(`Star it at ${sn.URL}, then run \`jsql star\` again · 点星后重跑 \`jsql star\``);
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
226
|
if (status === 'denied' || status === 'error') {
|
|
227
|
-
// 这不是「没 star」,是授权本身没走通(code
|
|
228
|
-
|
|
229
|
-
console.log(`Authorization ${
|
|
230
|
-
console.log('The link is single-use
|
|
227
|
+
// 这不是「没 star」,是授权本身没走通(code 过期、链接被重复打开、被拒绝)
|
|
228
|
+
const why = status === 'denied' ? 'declined 被拒绝' : 'failed 失败';
|
|
229
|
+
console.log(`Authorization ${why}: ${res.error || 'unknown reason'}`);
|
|
230
|
+
console.log('The link is single-use — run `jsql star` again · 链接一次性,请重跑 `jsql star`');
|
|
231
231
|
return;
|
|
232
232
|
}
|
|
233
233
|
if (status === 'expired') {
|
|
234
|
-
console.log('The link expired
|
|
234
|
+
console.log('The link expired 链接已过期 — run `jsql star` again · 请重跑');
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
|
-
console.log('Timed out
|
|
237
|
+
console.log('Timed out 已超时 — run `jsql star` again · 请重跑');
|
|
238
238
|
})
|
|
239
239
|
.command('bench', 'Insert + query benchmark against a data directory', (sub) => {
|
|
240
240
|
sub.option('n', { type: 'number', description: 'Number of rows (default 10000)' });
|
package/index.js
CHANGED
package/lib/star-nag.js
CHANGED
|
@@ -208,7 +208,7 @@ function nag(opts = {}) {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
out.write(`\n If JSQL-NEO saved you time, a star helps others find it: ${URL}\n`);
|
|
211
|
-
out.write(`
|
|
211
|
+
out.write(` 如果帮到了你,点个 star;运行 \`jsql star\` 确认 · JSQL_NO_STAR_NAG=1 关闭提示\n\n`);
|
|
212
212
|
|
|
213
213
|
// 只记录「有没有 star」,不再写 lastNagAt —— 没有节流,写它没有意义。
|
|
214
214
|
return true;
|
package/lib/wasm_client.js
CHANGED
|
@@ -725,24 +725,59 @@ class JSQL {
|
|
|
725
725
|
/**
|
|
726
726
|
* 校验 UPDATE 后的主键 / UNIQUE 值不与其它行冲突。
|
|
727
727
|
* 逐行比对(未建索引也正确);值未变化时跳过。
|
|
728
|
+
*
|
|
729
|
+
* 6.5.5 性能修复(对齐 native_client.js 的 6.4.8 修复,那次漏了 wasm 侧):
|
|
730
|
+
* 旧写法无条件 `this.find(table, {}, {limit: 1e9})` 把整张表导出成 JSON,
|
|
731
|
+
* 再逐字段 find/some。updateById 于是在 10 万行的表上要 ~590ms/次 ——
|
|
732
|
+
* 比读整张表还贵,而它本来只需要定位一行。
|
|
733
|
+
* UPDATE t SET age = 30 WHERE id = 1 ← age 不是键,不可能冲突,却付了全表钱
|
|
734
|
+
*
|
|
735
|
+
* 新逻辑(性能从 O(表行数) 降回 O(1)):
|
|
736
|
+
* 1. 只筛出 pk + unique 列,且**确实出现在 updateData 里**的(touched);
|
|
737
|
+
* 一个都没碰 → 直接 return,整张表一个字都不读;
|
|
738
|
+
* 2. 先用主键把**自己那一行**取回来(一条走 pk 的 find,limit 1,O(1))。
|
|
739
|
+
* 所有 touched 列的新值都和现值相同 → 值没变,同样直接 return
|
|
740
|
+
* (幂等更新不误报,也不再触发全表扫描);
|
|
741
|
+
* 3. 真的动了键,才退化成整表扫描逐字段比对(正确性优先)。
|
|
728
742
|
*/
|
|
729
743
|
async _assertNoKeyConflict(table, id, data, selfSeq) {
|
|
730
744
|
const schema = this._schemas[table];
|
|
731
745
|
if (!schema) return;
|
|
732
746
|
const fields = Object.keys(schema).filter(f => schema[f] && (schema[f].primaryKey || schema[f].unique));
|
|
733
747
|
if (!fields.length) return;
|
|
748
|
+
// 只在**确实要改主键 / UNIQUE 列**时才做冲突检查。
|
|
749
|
+
const touched = fields.filter(f => Object.prototype.hasOwnProperty.call(data, f));
|
|
750
|
+
if (!touched.length) return;
|
|
751
|
+
|
|
752
|
+
// 自己那一行:走主键的 find(limit 1),不整表导出。
|
|
753
|
+
let self = null;
|
|
754
|
+
try {
|
|
755
|
+
const pk = this._pkOf(table);
|
|
756
|
+
if (pk) {
|
|
757
|
+
const one = await this.find(table, { [pk]: this._coercePkId(table, id) }, { limit: 1, offset: 0 });
|
|
758
|
+
self = Array.isArray(one) ? (one[0] || null) : null;
|
|
759
|
+
}
|
|
760
|
+
} catch (_) {
|
|
761
|
+
self = null; // 取不到就走下面的全量比对
|
|
762
|
+
}
|
|
763
|
+
// 所有 touched 列的值都没变 → 幂等更新,不可能产生新冲突,无需扫表。
|
|
764
|
+
if (self && touched.every(f => self[f] === data[f])) return;
|
|
765
|
+
|
|
734
766
|
let rows;
|
|
735
767
|
try {
|
|
736
768
|
rows = await this.find(table, {}, { limit: 1e9, offset: 0 });
|
|
737
769
|
} catch (_) {
|
|
738
770
|
return; // 读不到就不拦,交给底层报错
|
|
739
771
|
}
|
|
772
|
+
if (!Array.isArray(rows)) return;
|
|
773
|
+
// 把自己那一行找出来并从比对中排除。注意:self 来自上一次 find,
|
|
774
|
+
// 与 rows 里的对象不是同一个引用,所以必须按 id 重新定位,不能直接 r !== self。
|
|
775
|
+
const selfRow = self || rows.find(r => r && String(r.id) === String(id)) || null;
|
|
740
776
|
for (const f of fields) {
|
|
741
777
|
if (!Object.prototype.hasOwnProperty.call(data, f)) continue;
|
|
742
778
|
const nv = data[f];
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
if (rows.some(r => r && r !== self && r[f] === nv)) {
|
|
779
|
+
if (selfRow && selfRow[f] === nv) continue; // 值没变
|
|
780
|
+
if (rows.some(r => r && r !== selfRow && r[f] === nv)) {
|
|
746
781
|
const e = new Error(`Duplicate entry '${nv}' for key '${f}'`);
|
|
747
782
|
e.code = 'ER_DUP_ENTRY';
|
|
748
783
|
e.errno = 1062;
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsql-neo",
|
|
3
|
-
"version": "6.5.
|
|
4
|
-
"description": "JSQL-NEO
|
|
3
|
+
"version": "6.5.5",
|
|
4
|
+
"description": "JSQL-NEO \u2014 Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -97,4 +97,4 @@
|
|
|
97
97
|
"devDependencies": {
|
|
98
98
|
"c8": "^10.1.2"
|
|
99
99
|
}
|
|
100
|
-
}
|
|
100
|
+
}
|
package/test/star-nag.test.js
CHANGED
|
@@ -89,13 +89,13 @@ async function main() {
|
|
|
89
89
|
console.log('\n--- 假 result.php 驱动真实 CLI ---');
|
|
90
90
|
const cases = [
|
|
91
91
|
['done + starred', { state: 'done', result: { starred: true, login: 'octocat', repo: 'vexify-org/JSQL-neo' } },
|
|
92
|
-
'Confirmed: octocat
|
|
92
|
+
'Confirmed: octocat starred vexify-org/JSQL-neo'],
|
|
93
93
|
['done + 未 star', { state: 'done', result: { starred: false, login: 'octocat', repo: 'vexify-org/JSQL-neo' } },
|
|
94
|
-
'
|
|
94
|
+
'Not starred yet 还没 star: octocat has not starred vexify-org/JSQL-neo'],
|
|
95
95
|
['denied', { state: 'denied', result: { error: 'access_denied' } },
|
|
96
|
-
'Authorization
|
|
96
|
+
'Authorization declined 被拒绝: access_denied'],
|
|
97
97
|
['error(code 过期/复用)', { state: 'error', result: { error: 'The code passed is incorrect or expired.' } },
|
|
98
|
-
'Authorization failed
|
|
98
|
+
'Authorization failed 失败: The code passed is incorrect or expired.'],
|
|
99
99
|
];
|
|
100
100
|
for (const [name, resp, expect] of cases) {
|
|
101
101
|
scenario = resp; pollsLeft = 0; after = null;
|
|
@@ -111,8 +111,8 @@ async function main() {
|
|
|
111
111
|
pollsLeft = 2;
|
|
112
112
|
after = { state: 'done', result: { starred: true, login: 'octocat', repo: 'vexify-org/JSQL-neo' } };
|
|
113
113
|
const out = await runCli(path.join(mrDir, 'slow'));
|
|
114
|
-
ok('等完 pending 后正确确认', out.includes('Confirmed: octocat
|
|
115
|
-
ok('全程没有误判成完成', !/
|
|
114
|
+
ok('等完 pending 后正确确认', out.includes('Confirmed: octocat starred vexify-org/JSQL-neo'), out.trim().slice(0, 200));
|
|
115
|
+
ok('全程没有误判成完成', !/Not starred yet/.test(out), out.trim().slice(0, 200));
|
|
116
116
|
ok('全程没有 undefined', !/undefined/.test(out), out.trim().slice(0, 200));
|
|
117
117
|
}
|
|
118
118
|
|