jsql-neo 4.1.2 → 4.2.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # JSQL-NEO v4.1.1
1
+ # JSQL-NEO v4.2.0
2
2
 
3
- Rust-powered embedded database with **three engines + SQL + Redis-style storage** in one npm package.
3
+ Rust-powered embedded database with **three engines + SQL + MySQL-compatible server + Redis-style storage** in one npm package.
4
4
 
5
5
  > **Docs & Help:** [https://help.vexify.top](https://help.vexify.top)
6
6
  > **GitHub:** [https://github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
@@ -33,7 +33,54 @@ await jsql.executeSQL(db, 'DELETE FROM users WHERE id = 2');
33
33
  await db.stop();
34
34
  ```
35
35
 
36
- Supported: `CREATE/DROP TABLE`, `INSERT` (multi-row, `ON DUPLICATE KEY UPDATE`), `SELECT` (`WHERE`/`ORDER BY`/`LIMIT`/`OFFSET`/`GROUP BY`/`HAVING`/aggregates), `UPDATE`, `DELETE`, prepared statements with `?` placeholders.
36
+ Supported SQL: `CREATE/DROP TABLE` (incl. `IF [NOT] EXISTS`), `CREATE/DROP DATABASE`, `USE` / `SHOW DATABASES` / `SHOW TABLES [FROM db]`, `INSERT` (multi-row, `ON DUPLICATE KEY UPDATE`), `SELECT` (`WHERE` / `ORDER BY` / `LIMIT` / `OFFSET` / `GROUP BY` / `HAVING` / aggregates `COUNT`/`SUM`/`AVG`/`MIN`/`MAX` / `DISTINCT` / `JOIN` / `UNION [ALL]` / `CASE WHEN` / subqueries `IN (…)` & scalar `(SELECT …)` / `BETWEEN`/`LIKE`/`IS NULL`), `UPDATE`, `DELETE`, `BEGIN`/`COMMIT`/`ROLLBACK`, prepared statements with `?` placeholders.
37
+
38
+ Type names accept MySQL aliases: `INTEGER/INT/BIGINT/TINYINT/SMALLINT`, `STRING/TEXT/VARCHAR(n)/CHAR(n)`, `FLOAT/DOUBLE/REAL/NUMERIC/DECIMAL`, `BOOLEAN/BOOL`, `DATE/DATETIME/TIMESTAMP`, `JSON/OBJECT/ARRAY`.
39
+
40
+ ## MySQL-Compatible Server (multi-database)
41
+
42
+ A native MySQL protocol server on TCP 3306 — any `mysql` / `mysql2` / GUI client connects directly.
43
+
44
+ ```bash
45
+ npx jsql server start --port 3306 --data-dir ./data # 后台常驻
46
+ npx jsql server status # 状态
47
+ npx jsql server stop # 停止
48
+ ```
49
+
50
+ **Multi-database & 一键建表:** each database is an independent directory (`<dataDir>/<db>`).
51
+
52
+ ```sql
53
+ -- 随意连接即可直接建表(自动落到默认库 default)
54
+ CREATE TABLE users (id INTEGER PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));
55
+
56
+ -- 一条 SQL 跨库建库+建表(库不存在自动创建)
57
+ CREATE TABLE shop.orders (id INTEGER PRIMARY KEY, amount FLOAT, `user` STRING);
58
+
59
+ -- 连接时指定尚不存在的库会自动创建:mysql -uroot -D mydb
60
+ -- 查询/增删改也能带库前缀:INSERT INTO shop.orders ..., SELECT * FROM shop.orders, SHOW TABLES FROM shop;
61
+ ```
62
+
63
+ - 连接指定未存在的库 → **自动建库**,无需先 `CREATE DATABASE`
64
+ - 未 `USE` 任何库 → 落到默认库,可直接 `CREATE TABLE`
65
+ - 全量多库 DDL/DML 支持 `db.table` 点号语法 + `SHOW TABLES FROM db`
66
+ - **多用户 + 库级权限**: 每个用户可配置可访问的数据库白名单(`{ user: { password, databases: [...] } }`)
67
+
68
+ ```json
69
+ { "port": 3306, "dataDir": "/var/lib/jsql",
70
+ "auth": { "admin": { "password": "secret", "databases": ["shop", "blog"] }, "reader": "readpass" } }
71
+ ```
72
+
73
+ ## Transactions
74
+
75
+ ```js
76
+ db.begin(); // 默认 REPEATABLE_READ:begin 时保存行快照
77
+ db.insert('t', { id: 2 });
78
+ db.rollback(); // 删除新增、还原更新、恢复自增 —— 回滚生效
79
+ db.begin(); db.insert('t', { id: 3 }); db.commit();
80
+ // 也支持 SQL:BEGIN ... COMMIT / ROLLBACK
81
+ ```
82
+
83
+ `begin(isolationLevel)` 可选 `'REPEATABLE_READ'`(快照回滚,默认)或 `'READ_COMMITTED'`(仅回滚自增)。
37
84
 
38
85
  ## Redis-Style Storage Modes
39
86
 
@@ -107,29 +154,58 @@ db.save();
107
154
  | `updateById(table, id, data)` | ✅ | ✅ | ✅ | O(1) PK update |
108
155
  | `removeById(table, id)` | ✅ | ✅ | ✅ | O(1) PK delete |
109
156
  | `dropTable(name)` | ✅ | ✅ | ✅ | Remove table |
157
+ | `insertMany(table, items)` | ✅ | ✅ | ✅ | Batch insert |
158
+ | `update(table, query, updates)` | ✅ | ✅ | ✅ | Batch update by query |
159
+ | `delete(table, query)` | ✅ | ✅ | ✅ | Batch delete by query |
160
+ | `truncate(table)` | ✅ | ✅ | ✅ | Delete all rows |
161
+ | `begin()/commit()/rollback()` | ✅ | ✅ | ✅ | Transactions (snapshot rollback) |
110
162
  | `executeSQL(db, sql, params?)` | ✅ | ✅ | ✅ | Run SQL statements |
111
163
 
112
164
  ## Schema Field Options
113
165
 
114
166
  ```js
115
167
  {
116
- type: 'string' | 'integer' | 'float' | 'boolean',
117
- primaryKey: true, // PK field (auto-indexed)
168
+ type: 'string' | 'integer' | 'float' | 'boolean' | 'date' | 'object' | 'array',
169
+ primaryKey: true, // PK field (auto-indexed) — `primary: true` 也接受
118
170
  autoIncrement: true, // Auto-generate integer PK
171
+ unique: true, // Unique constraint (auto-indexed)
119
172
  length: 32, // Max string length
120
173
  default: 'value', // Default value
121
- nullable: true // Allow null
174
+ nullable: true, // Allow null
175
+ required: true, // Not-null constraint
176
+ foreignKey: { table, field, onDelete }, // FK constraint
177
+ check: (val) => bool, // CHECK constraint
178
+ computed: (row) => val // Computed field
122
179
  }
123
180
  ```
124
181
 
125
182
  ## Features
126
183
 
127
- - Three engines: Native (N-API Rust), WASM (wasm-pack Rust), Pure JS (local JSON)
128
- - SQL engine with prepared statements
129
- - Redis-style hybrid/disk storage: memory-first + async flush + LRU eviction + lazy reload
130
- - O(1) primary key hash index (`FxHashMap` / `Map`)
131
- - B-Tree indexing for range queries
132
- - WAL + snapshot crash recovery (server engine)
133
- - Batch insert / update / delete
134
- - Cursor-based pagination
135
- - Transaction support (server engine)
184
+ **Engines & Storage**
185
+ - Three engines with one shared API: Native (N-API Rust, fastest), WASM (wasm-pack, zero native deps), Pure JS (`Database`)
186
+ - Redis-style storage modes: `memory` / `hybrid` (async flush + LRU eviction + lazy reload) / `disk` (fast flush, memory as cache)
187
+ - Atomic per-table files (`<dir>/<table>.jsql.json` + `meta.json`, tmp + rename)
188
+ - WAL + snapshot crash recovery
189
+ - O(1) primary-key hash index (`FxHashMap` / `Map`) + B-Tree range indexes
190
+
191
+ **SQL**
192
+ - Full statement coverage: DDL / DML / SELECT (aggregates, `GROUP BY`/`HAVING`, `JOIN`, `UNION`, `CASE WHEN`, subqueries) / transactions
193
+ - Prepared statements with `?` placeholders
194
+ - MySQL type-name aliases + `db.table` cross-database syntax + `SHOW TABLES FROM db`
195
+
196
+ **MySQL Server & CLI**
197
+ - MySQL protocol server (TCP 3306), compatible with `mysql`/`mysql2`/GUI clients
198
+ - Multi-database (one dir per DB), auto-create on connect / on `db.table` usage, default-DB fallback for one-click `CREATE TABLE`
199
+ - Multi-user authentication with per-database access lists (`1044`/`1049` enforcement)
200
+ - `jsql server start/stop/status` background daemon with graceful shutdown
201
+
202
+ **Data API**
203
+ - Typed schema with constraints: PK / auto-increment / unique / required / length / default / FK / CHECK / computed fields
204
+ - Batch insert / update / delete, cursor-based pagination, upsert
205
+ - Transactions with snapshot rollback (default `REPEATABLE_READ`)
206
+ - CSV import/export, JSON schema validation, soft delete, versioning, views, triggers, attachable databases
207
+ - Hooks & change events (`beforeInsert`/`afterUpdate`/…) for plugins and audit
208
+
209
+ **Security**
210
+ - SQL injection hardening: comments/`LOAD_FILE`/dangerous statements blocked by default, ReDoS-safe regex
211
+ - Rate-limited auth (max auth fails), oversized-row protection, safe name/path validation (`..` blocked)
package/lib/database.js CHANGED
@@ -92,8 +92,8 @@ class Database {
92
92
  this._walPath = this._filePath ? this._filePath + '.wal' : null;
93
93
  this._walOps = []; // WAL 操作日志
94
94
 
95
- // 事务隔离
96
- this._isolationLevel = options.isolationLevel || 'READ_COMMITTED';
95
+ // 事务隔离:默认 REPEATABLE_READ(begin 时保存行快照,rollback 可回滚)
96
+ this._isolationLevel = options.isolationLevel || 'REPEATABLE_READ';
97
97
 
98
98
  // JSql 格式 (binary block-based)
99
99
  this._jsqlMode = this._filePath && this._filePath.endsWith('.jsql');
@@ -1286,6 +1286,49 @@ class Database {
1286
1286
  return { ok: true, count: removed };
1287
1287
  }
1288
1288
 
1289
+ insertMany(tableName, items) {
1290
+ const table = this._ensureTable(tableName);
1291
+ if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
1292
+ const arr = Array.isArray(items) ? items : [items];
1293
+ if (!this._runHooks('beforeInsert', [tableName, arr])) return [];
1294
+ const inserted = table.insertMany(arr);
1295
+ this._emit('insert', { table: tableName, count: arr.length });
1296
+ this._runHooks('afterInsert', [tableName, arr, inserted]);
1297
+ this._markDirty(tableName);
1298
+ return inserted;
1299
+ }
1300
+
1301
+ update(tableName, query, updates) {
1302
+ const table = this._ensureTable(tableName);
1303
+ if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
1304
+ if (!this._runHooks('beforeUpdate', [tableName, query, updates])) return 0;
1305
+ const count = table.update(query, updates);
1306
+ this._emit('update', { table: tableName, query, count });
1307
+ this._runHooks('afterUpdate', [tableName, query, updates, count]);
1308
+ this._markDirty(tableName);
1309
+ return count;
1310
+ }
1311
+
1312
+ delete(tableName, query = {}) {
1313
+ const table = this._ensureTable(tableName);
1314
+ if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
1315
+ if (!this._runHooks('beforeDelete', [tableName, query])) return 0;
1316
+ const count = table.remove(query);
1317
+ this._emit('delete', { table: tableName, query, count });
1318
+ this._runHooks('afterDelete', [tableName, query, count]);
1319
+ this._markDirty(tableName);
1320
+ return count;
1321
+ }
1322
+
1323
+ truncate(tableName) {
1324
+ const table = this._ensureTable(tableName);
1325
+ if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
1326
+ const count = table.truncate();
1327
+ this._emit('delete', { table: tableName, count });
1328
+ this._markDirty(tableName);
1329
+ return count;
1330
+ }
1331
+
1289
1332
  async stop() {
1290
1333
  this._runHooks('onStop', []);
1291
1334
  this._emit('stop', {});
package/lib/table.js CHANGED
@@ -133,10 +133,11 @@ class Table {
133
133
  this._dateFields = {}; // { fieldName: 'date'|'datetime'|'timestamp' }
134
134
 
135
135
  for (const [field, def] of Object.entries(schema)) {
136
- if (def.primaryKey) this._primaryKey = field;
136
+ const isPk = !!(def.primaryKey || def.primary === true);
137
+ if (isPk) this._primaryKey = field;
137
138
  if (def.autoIncrement) {
138
139
  this._autoIncrementField = field;
139
- if (def.primaryKey) this._primaryKey = field;
140
+ if (isPk) this._primaryKey = field;
140
141
  }
141
142
  if (def.default !== undefined) {
142
143
  this._defaults[field] = def.default;
@@ -177,7 +178,7 @@ class Table {
177
178
  _autoCreateBTrees() {
178
179
  for (const [field, def] of Object.entries(this._schema)) {
179
180
  if (field === '_softDelete') continue;
180
- if (def.primaryKey || def.unique) {
181
+ if (def.primaryKey || def.primary === true || def.unique) {
181
182
  this._btrees[field] = new BTree(64, true);
182
183
  }
183
184
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
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
  "bin": {