jsql-neo 6.4.8 → 6.5.0-beta.3

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
@@ -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.8** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
6
+ > **v6.5.0-beta.3** — pre-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
  ![Engines](https://img.shields.io/badge/engines-Native%20%7C%20WASM%20%7C%20Pure%20JS-7ee787)
9
9
  ![MySQL](https://img.shields.io/badge/protocol-MySQL%20compatible-1f6feb)
@@ -36,12 +36,18 @@ irm jsql.vexify.top/setup | iex
36
36
  npm install jsql-neo
37
37
  ```
38
38
 
39
- **JSQL-NEO** is a Rust-powered embedded database with **zero runtime dependencies** that speaks MySQL, PostgreSQL, MongoDB, and Redis wire protocols — all on the same engine, same data, one port. It runs in Node.js, CLI, Web UI, and the browser (WASM).
39
+ **JSQL-NEO** is a Rust-powered embedded database with **zero runtime dependencies**. It runs in Node.js, CLI, Web UI, and the browser (WASM).
40
40
 
41
- - **Any client, same data** — MySQL, PostgreSQL, MongoDB, Redis tools connect simultaneously on **one port**
41
+ - **MySQL wire protocol, ready to use** — `jsql serve` starts it; `mysql2` / most MySQL clients connect directly
42
42
  - **Zero dependencies** — no native libs to compile, no system deps to install
43
- - **Runs everywhere** — Node.js native addon, pure JS fallback, WASM for the browser
44
- - **~2× faster than better-sqlite3** — Rust core via N-API
43
+ - **Three engines, same API** — `native` (Rust N-API), `wasm` (same Rust core), `js` (pure JS)
44
+ - **~2× faster than better-sqlite3** on indexed point lookups
45
+
46
+ > **About the other wire protocols** — PostgreSQL, MongoDB and Redis protocol stacks
47
+ > ship as libraries (`lib/pg_server`, `lib/mongo_server`, `lib/redis_server`) and each
48
+ > listens on its own port; there is **no single-port multi-protocol mode**, and the CLI
49
+ > exposes no entry point for them. MongoDB additionally does not yet complete a
50
+ > handshake with the modern official `mongodb` driver. Treat them as experimental.
45
51
 
46
52
  ---
47
53
 
@@ -88,36 +94,35 @@ console.log(rows); // [[1, 'Alice']]
88
94
  db.stop();
89
95
  ```
90
96
 
91
- ### One port, four protocols
97
+ ### Wire protocol server
92
98
 
93
99
  ```bash
94
- npx jsql-neo serve --port 3306
100
+ jsql serve --port 3306 --data-dir ./data
95
101
  ```
96
102
 
97
103
  ```bash
98
- # MySQL mysql -h 127.0.0.1 -P 3306
99
- # PostgreSQL psql -h 127.0.0.1 -p 3306
100
- # MongoDB mongosh mongodb://127.0.0.1:3306
101
- # Redis redis-cli -p 3306
104
+ # MySQL clients (mysql CLI, mysql2, DBeaver, DataGrip …) connect here
105
+ mysql -h 127.0.0.1 -P 3306
102
106
  ```
103
107
 
104
- All four clients connect to the **same database** on the same port.
108
+ Only MySQL is served by the CLI today. The PostgreSQL / Redis / MongoDB stacks are
109
+ available as libraries — see the note near the top of this file.
105
110
 
106
111
  ### Browser / WASM
107
112
 
108
113
  ```js
109
- import init, { Database } from 'jsql-neo/wasm';
114
+ import init, { JSQL } from 'jsql-neo/wasm/browser.mjs';
110
115
  await init();
111
- const db = new Database(':memory:');
112
- // same API as Node.js
116
+ const db = new JSQL();
117
+ // same API as the Node.js native client
113
118
  ```
114
119
 
115
120
  ### 🦀 NativeJSQL
116
121
 
117
122
  Pure Rust native bindings — the highest-performance way to run JSQL-NEO:
118
123
 
119
- - **Zero abstraction overhead** — direct calls into the Rust core
120
- - **Batch operations** — up to **1.54M rows/sec** peak throughput
124
+ - **Low abstraction overhead** — direct calls into the Rust core
125
+ - **Batch operations** — ~700k rows/sec measured on 200k rows (`INSERT` in 5k batches)
121
126
  - **Automatic memory management** with smart caching
122
127
 
123
128
  **Install:**
@@ -129,8 +134,9 @@ npm install jsql-neo
129
134
  **Usage:**
130
135
 
131
136
  ```javascript
132
- import { Database } from 'jsql-neo';
133
- const db = new Database('mydb.jsql');
137
+ const { NativeJSQL } = require('jsql-neo');
138
+ const db = new NativeJSQL(); // :memory: — persist by passing a path
139
+ await db.start();
134
140
  ```
135
141
 
136
142
  ### Storage modes
package/bin/jsql CHANGED
@@ -5,6 +5,12 @@ const yaggs = require('@vexify-org/yaggs');
5
5
  const { ModuleManager } = require('../lib/mod');
6
6
  const Database = require('../lib/database');
7
7
  const migrate = require('../lib/migrate');
8
+ const { nag, setStarred, readConfig } = require('../lib/star-nag');
9
+
10
+ // star 提示:任何异常都不该挡住命令本身
11
+ if (!process.env.JSQL_NO_STAR_NAG && process.argv[2] !== 'star') {
12
+ try { nag(); } catch (e) { /* 提示失败与命令无关 */ }
13
+ }
8
14
 
9
15
  function printModules(list) {
10
16
  if (list.length === 0) {
@@ -168,6 +174,23 @@ const cli = yaggs({ pkg: require('../package.json') })
168
174
  console.log(`jsql-neo MySQL server on ${options.host}:${options.port}${options.dataDir ? ' (data: ' + options.dataDir + ')' : ' (in-memory)'}`);
169
175
  setInterval(() => {}, 1 << 30);
170
176
  })
177
+ .command('star', 'Confirm whether you starred this repo (opens browser once)', (sub) => {
178
+ sub.option('reset', { type: 'boolean', description: 'Forget the saved star state and re-check' });
179
+ }, async (argv) => {
180
+ const sn = require('../lib/star-nag');
181
+ if (argv.reset) sn.setStarred(false);
182
+ const c = sn.readConfig();
183
+ if (c.starred) {
184
+ console.log('Already confirmed starred (config: ' + sn.configPath() + ')');
185
+ console.log('Run with --reset to re-check.');
186
+ return;
187
+ }
188
+ console.log('Opening browser to confirm your star…');
189
+ const nonceUrl = sn.startOauthCheck(); // 打开浏览器 + 后台轮询写回 ~/.jsqlmr
190
+ if (!process.stdout.isTTY) console.log(' ' + nonceUrl);
191
+ console.log('After you authorize, this config is updated: ' + sn.configPath());
192
+ console.log('Run `jsql star` again afterwards to see the result.');
193
+ })
171
194
  .command('bench', 'Insert + query benchmark against a data directory', (sub) => {
172
195
  sub.option('n', { type: 'number', description: 'Number of rows (default 10000)' });
173
196
  sub.option('data-dir', { type: 'string', description: 'Data directory (default :memory:)' });
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * JSQL-NEO v6.4.8 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.5.0-beta.3 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
@@ -0,0 +1,206 @@
1
+ 'use strict';
2
+ /**
3
+ * CLI 启动时的 star 提示。
4
+ *
5
+ * 行为:
6
+ * 1. 已确认 star 过 → 什么都不说
7
+ * 2. 距上次提示不足 N 天 → 什么都不说
8
+ * 3. 否则打印一行提示,并在配置里记下时间
9
+ *
10
+ * star 检测:若环境里有 GitHub token(GITHUB_TOKEN / GH_TOKEN),
11
+ * 就问 GitHub「这个用户 star 过没有」。查到了就记住,以后彻底闭嘴 ——
12
+ * 用户已经 star 过了,再提就是噪音。
13
+ *
14
+ * 配置文件:~/.jsqlmr(JSON)。可用 JSQL_MR_PATH 覆盖。
15
+ *
16
+ * 全程不影响命令本身:任何异常都吞掉,绝不因为提示语让 CLI 起不来。
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const os = require('os');
21
+ const path = require('path');
22
+
23
+ const REPO = 'vexify-org/JSQL-neo';
24
+ const URL = `https://github.com/${REPO}`;
25
+ const NAG_INTERVAL_DAYS = 30;
26
+
27
+ const AUTH_BASE = process.env.JSQL_AUTH_BASE || 'https://auth.vexify.top';
28
+ const AUTH_START = AUTH_BASE + '/start.php';
29
+ const AUTH_RESULT = AUTH_BASE + '/result.php';
30
+
31
+ /**
32
+ * 发起 OAuth 浏览器流并在后台轮询结果。
33
+ *
34
+ * 绝不阻塞命令:开完浏览器就返回,轮询用 unref 过的定时器跑。
35
+ * 拿到结果写进 ~/.jsqlmr(starred: true/false)。
36
+ * 进程提前退出就放弃这一次,下次启动会再提示(30 天节流管频率)。
37
+ */
38
+ function startOauthCheck() {
39
+ const crypto = require('crypto');
40
+ const nonce = crypto.randomBytes(24).toString('base64url');
41
+ const url = AUTH_START + '?nonce=' + nonce;
42
+
43
+ // 开浏览器:三平台各一个命令;失败就只打印链接让人手动点
44
+ const cmd = process.platform === 'win32' ? 'start'
45
+ : process.platform === 'darwin' ? 'open' : 'xdg-open';
46
+ try {
47
+ require('child_process').spawn(cmd, [url], {
48
+ detached: true, stdio: 'ignore', shell: process.platform === 'win32',
49
+ }).unref();
50
+ } catch (e) {
51
+ console.log(' Open this to confirm your star: ' + url);
52
+ }
53
+
54
+ // 后台轮询,最多约 90 秒
55
+ let tries = 0;
56
+ const t = setInterval(() => {
57
+ if (++tries > 45) { clearInterval(t); return; }
58
+ const { execFile } = require('child_process');
59
+ execFile('curl', ['-sS', '-m', '5', AUTH_RESULT + '?nonce=' + nonce],
60
+ { timeout: 6000, encoding: 'utf8' }, (err, stdout) => {
61
+ if (err) return;
62
+ let j = null;
63
+ try { j = JSON.parse(stdout); } catch (e) { return; }
64
+ if (!j || j.state !== 'done' || !j.result) return;
65
+ clearInterval(t);
66
+ const cfg = readConfig();
67
+ cfg.oauthCheckedAt = Date.now();
68
+ cfg.login = j.result.login || undefined;
69
+ cfg.starred = !!j.result.starred;
70
+ if (cfg.starred) cfg.starredAt = Date.now();
71
+ writeConfig(cfg);
72
+ });
73
+ }, 2000);
74
+ t.unref();
75
+ return url;
76
+ }
77
+
78
+ function configPath() {
79
+ return process.env.JSQL_MR_PATH || path.join(os.homedir(), '.jsqlmr');
80
+ }
81
+
82
+ function readConfig() {
83
+ try {
84
+ const raw = fs.readFileSync(configPath(), 'utf8');
85
+ const c = JSON.parse(raw);
86
+ return (c && typeof c === 'object') ? c : {};
87
+ } catch (e) {
88
+ return {}; // 不存在 / 坏了 / 没权限,都当新用户
89
+ }
90
+ }
91
+
92
+ function writeConfig(cfg) {
93
+ try {
94
+ fs.writeFileSync(configPath(), JSON.stringify(cfg, null, 2) + '\n');
95
+ } catch (e) {
96
+ /* 写不了就算了,不该因此报错 */
97
+ }
98
+ }
99
+
100
+ /** 同步问一下 GitHub:这个身份 star 过没有。查不到就返回 null(三态)。 */
101
+ /**
102
+ * 找一个可用的 GitHub 凭据。
103
+ * 1. GITHUB_TOKEN / GH_TOKEN 环境变量
104
+ * 2. 官方 gh CLI(`gh auth token`)—— 多数开发机上它已经登录过了,
105
+ * 不该要求用户为了看一句提示语再去配环境变量
106
+ * 3. 都没有 → null
107
+ */
108
+ function findToken() {
109
+ const env = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
110
+ if (env) return env;
111
+ try {
112
+ const { execFileSync } = require('child_process');
113
+ const t = execFileSync('gh', ['auth', 'token'], {
114
+ stdio: ['ignore', 'pipe', 'ignore'],
115
+ timeout: 2500,
116
+ encoding: 'utf8',
117
+ }).trim();
118
+ if (t) return t;
119
+ } catch (e) {
120
+ // 没装 gh / 没登录 / 太慢 —— 都当作没有
121
+ }
122
+ return null;
123
+ }
124
+
125
+ /**
126
+ * 尽力而为地问一次「这个身份 star 过没有」。查不到返回 null(三态)。
127
+ *
128
+ * 只在「真的准备显示提示语时」才发这个网络请求;已标记 starred 或被
129
+ * 30 天节流挡掉时一次请求都不会发。
130
+ *
131
+ * @returns {boolean|null} true=star 过 false=没 star null=无法确认
132
+ */
133
+ function checkStarred() {
134
+ const token = findToken();
135
+ if (!token) return null; // 没有可用凭据 → 无法确认
136
+ // 用同步 API:CLI 启动阶段还没有 event loop 可用
137
+ try {
138
+ const { execFileSync } = require('child_process');
139
+ const out = execFileSync('curl', [
140
+ '-sS', '-m', '4',
141
+ '-H', `Authorization: Bearer ${token}`,
142
+ '-H', 'Accept: application/vnd.github+json',
143
+ '-H', 'X-GitHub-Api-Version: 2022-11-28',
144
+ `https://api.github.com/user/starred/${REPO}`,
145
+ ], { stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000 }).toString();
146
+ if (out.trim() === '') return true; // 200 + 空数组 = 列了但没有这个仓库
147
+ const j = JSON.parse(out);
148
+ // GitHub 出错时返回的是 {"message":"Bad credentials"} 之类,不是数组。
149
+ // 那说明 token 无效/过期 —— 属于「无法确认」,不能当成「没 star」,
150
+ // 否则会永远提示下去。
151
+ if (j && !Array.isArray(j) && typeof j.message === 'string') return null;
152
+ return Array.isArray(j) && j.length > 0;
153
+ } catch (e) {
154
+ return null; // 网络不通 / 401 / 没装 curl → 不确定
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @param {object} [opts]
160
+ * @param {NodeJS.WritableStream} [opts.out] 输出流(默认 stdout)
161
+ * @param {boolean} [opts.force] 忽略节流,强制提示一次
162
+ * @returns {boolean} 是否真的打印了
163
+ */
164
+ function nag(opts = {}) {
165
+ const out = opts.out || process.stdout;
166
+ const cfg = readConfig();
167
+
168
+ if (!opts.force && cfg.starred) return false;
169
+
170
+ const last = Number(cfg.lastNagAt) || 0;
171
+ if (!opts.force && last && Date.now() - last < NAG_INTERVAL_DAYS * 86400000) {
172
+ // 顺便有机会补记 star 状态(不打印)
173
+ const s = checkStarred();
174
+ if (s) { cfg.starred = true; writeConfig(cfg); }
175
+ return false;
176
+ }
177
+
178
+ // 有 token 就先查,省得对已经 star 的人重复啰嗦
179
+ const starred = checkStarred();
180
+ if (starred) {
181
+ cfg.starred = true;
182
+ cfg.starredAt = Date.now();
183
+ writeConfig(cfg);
184
+ return false; // 已 star,闭嘴
185
+ }
186
+
187
+ out.write(`\n If JSQL-NEO saved you time, a star helps others find it: ${URL}\n`);
188
+ out.write(` Run \`jsql star\` to confirm and stop seeing this (or set JSQL_NO_STAR_NAG=1 to hide)\n\n`);
189
+ startOauthCheck();
190
+
191
+ cfg.lastNagAt = Date.now();
192
+ writeConfig(cfg);
193
+
194
+ return true;
195
+ }
196
+
197
+ /** 供 `jsql star` 之类的命令用:手动确认 / 取消 */
198
+ function setStarred(v) {
199
+ const cfg = readConfig();
200
+ cfg.starred = !!v;
201
+ if (v) cfg.starredAt = Date.now();
202
+ writeConfig(cfg);
203
+ return cfg.starred;
204
+ }
205
+
206
+ module.exports = { nag, checkStarred, startOauthCheck, readConfig, setStarred, configPath, REPO, URL };
package/lib/table.js CHANGED
@@ -146,6 +146,20 @@ class Table {
146
146
  this._dateFields = {}; // { fieldName: 'date'|'datetime'|'timestamp' }
147
147
 
148
148
  for (const [field, def] of Object.entries(schema)) {
149
+ // schema 字段名归一化:驼峰 primaryKey/autoIncrement 与下划线
150
+ // primary_key/auto_increment 两种写法都要认。
151
+ //
152
+ // 此前这里只认驼峰,而 native_client 在交给 Rust 之前会归一化
153
+ // (lib/native_client.js 里 primary_key → primaryKey)。结果:
154
+ // README 与官方示例用的下划线写法,在 native 下正常,在纯 JS 下
155
+ // **静默失效** —— 自增 id 不生成,find({id:1}) 永远返回空,
156
+ // 而且不报任何错。这是纯 JS 与 native 的行为分歧。
157
+ if (def && typeof def === 'object' && !Array.isArray(def)) {
158
+ if (def.primary_key !== undefined && def.primaryKey === undefined) def.primaryKey = def.primary_key;
159
+ if (def.auto_increment !== undefined && def.autoIncrement === undefined) def.autoIncrement = def.auto_increment;
160
+ if (def.primary_key !== undefined) delete def.primary_key;
161
+ if (def.auto_increment !== undefined) delete def.auto_increment;
162
+ }
149
163
  const isPk = !!(def.primaryKey || def.primary === true);
150
164
  if (isPk) this._primaryKey = field;
151
165
  if (def.autoIncrement) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "6.4.8",
3
+ "version": "6.5.0-beta.3",
4
4
  "description": "JSQL-NEO — 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",
@@ -151,6 +151,43 @@ function ok(name, cond, extra) {
151
151
  await db.stop();
152
152
  }
153
153
 
154
+ /* ---- schema 字段名下划线写法:两引擎必须一致(静默正确性 bug)---- */
155
+ /*
156
+ * README 与官方示例用下划线写法(primary_key / auto_increment)。
157
+ * native_client 交给 Rust 前会归一化,纯 JS 的 table.js 此前不认 ——
158
+ * 结果纯 JS 下自增 id 根本不生成、find({id:1}) 永远空,且**不报任何错**。
159
+ */
160
+ {
161
+ const SNAKE = { id: { type: 'integer', primary_key: true, auto_increment: true }, name: { type: 'string' } };
162
+
163
+ // native
164
+ {
165
+ const d = new JSQL();
166
+ await d.start();
167
+ await d.createTable('snk', SNAKE);
168
+ await d.insert('snk', [{ name: 'a' }, { name: 'b' }]);
169
+ await new Promise(r => setTimeout(r, 200));
170
+ const rows = await d.find('snk', {});
171
+ const first = rows.find(r => r.name === 'a');
172
+ ok('schema 下划线写法 · native:自增 id 生成', !!(first && first.id !== undefined), rows);
173
+ const got = await d.findById('snk', first && first.id);
174
+ ok('schema 下划线写法 · native:findById 命中', !!(got && got.name === 'a'), got);
175
+ await d.stop();
176
+ }
177
+
178
+ // pure JS
179
+ {
180
+ const Database = require(path.join(ROOT, 'lib', 'database'));
181
+ const d = new Database(':memory:');
182
+ await d.createTable('snk', SNAKE);
183
+ await d.insert('snk', [{ name: 'a' }, { name: 'b' }]);
184
+ const rows = await d.find('snk', {});
185
+ const first = rows.find(r => r.name === 'a');
186
+ ok('schema 下划线写法 · pure-js:自增 id 生成', !!(first && first.id !== undefined), rows);
187
+ const got = await d.findById('snk', first && first.id);
188
+ ok('schema 下划线写法 · pure-js:findById 命中', !!(got && got.name === 'a'), got);
189
+ }
190
+ }
154
191
  console.log(`\n${passed} passed, ${failed} failed`);
155
192
  process.exit(failed ? 1 : 0);
156
193
  })().catch(e => {