jsql-neo 6.0.0-beta2 → 6.0.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
@@ -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. The browser. **And it fits in one npm package.**
5
5
 
6
- > **v5.6.0** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
6
+ > **v6.0.1** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
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)
@@ -124,6 +124,7 @@ For the complete Chinese edition of every topic below, jump to the [中文版](#
124
124
  - [Migration Tools](#migration-tools)
125
125
  - [Storage & Performance](#storage--performance)
126
126
  - [Security](#security)
127
+ - [Plugin System](#plugin-system)
127
128
  - [Errors](#errors)
128
129
  - [TypeScript](#typescript)
129
130
  - [Architecture](#architecture)
@@ -232,7 +233,7 @@ npm install && npm run build # option 3: from source
232
233
  Verify:
233
234
 
234
235
  ```bash
235
- node -e "console.log(require('jsql-neo/package.json').version)" # 5.6.0
236
+ node -e "console.log(require('jsql-neo/package.json').version)" # 6.0.1
236
237
  ```
237
238
 
238
239
  ### 30-second demo
@@ -410,7 +411,7 @@ Auth failure returns the standard `Access denied for user 'x'@'...' (using passw
410
411
 
411
412
  The server answers the system probes ORMs and GUI tools run at connect time:
412
413
 
413
- - `SELECT VERSION()` → `8.0.0-jsql-neo`
414
+ - `SELECT VERSION()` → `8.0.1-jsql-neo`
414
415
  - System variables: `@@version`, `@@version_comment`, …
415
416
  - `SHOW DATABASES / SHOW TABLES / SHOW COLUMNS / SHOW CREATE TABLE`
416
417
  - `information_schema.tables / columns / statistics`
@@ -1064,7 +1065,7 @@ SELECT AGE('2026-08-12', '2020-01-01'); -- '6 years 7 mons 11 days'
1064
1065
  **Conditional / null** — `IF(cond, a, b)`, `IIF`, `IFNULL(a, b)`, `COALESCE(...)`, `NULLIF`,
1065
1066
  `ISNULL`, `CASE WHEN ... THEN ... ELSE ... END` (search/simple form, works with aggregates).
1066
1067
 
1067
- **System & misc** — `VERSION()` → `8.0.0-jsql-neo`, `DATABASE()`, `USER()`/`CURRENT_USER`,
1068
+ **System & misc** — `VERSION()` → `8.0.1-jsql-neo`, `DATABASE()`, `USER()`/`CURRENT_USER`,
1068
1069
  `LAST_INSERT_ID()`, `ROW_COUNT()`, `CAST`/`CONVERT`, `JSON_EXTRACT(doc, '$.path')`,
1069
1070
  `JSON_OBJECT`/`JSON_ARRAY`, `JSON_UNQUOTE`, `JSON_CONTAINS`, `JSON_SET/INSERT/REPLACE/REMOVE`,
1070
1071
  `UUID()`, `TYPEOF(x)`, `PRINT(x)`.
@@ -1091,15 +1092,21 @@ ORDER BY avg_sal DESC;
1091
1092
 
1092
1093
  ### Operators
1093
1094
 
1094
- **Arithmetic** — `+ - * / % MOD DIV ^` (power, PG style).
1095
+ **Arithmetic** — `+ - * / %`,以及 `MOD` / `DIV` / `POWER()`。
1095
1096
 
1096
1097
  **Comparison** — `= <> != < <= > >=`, `IS NULL`, `IS TRUE/FALSE` (three-valued),
1097
1098
  `BETWEEN a AND b`, `IN (...)`, `LIKE`/`NOT LIKE`, `ILIKE` (case-insensitive),
1098
- `RLIKE`/`REGEXP`, `~ ~* !~ !~*` (PG regex), `EXISTS`, `ANY`/`ALL`/`SOME`, `<=>` (NULL-safe).
1099
+ `RLIKE`/`REGEXP`, `EXISTS`, `= ANY` / `> ALL` / `SOME`.
1099
1100
 
1100
- **Logic** — `AND OR NOT XOR` (short-circuit), MySQL aliases `&&` `||`.
1101
+ **Logic** — `AND OR NOT` (short-circuit).
1101
1102
 
1102
- **Bitwise** — `& | ^ ~ << >>`.
1103
+ **Bitwise** — `& | ^ ~ << >>`(`^` 是按位异或,`~` 是按位取反)。
1104
+
1105
+ > **运算符方言说明。** `^` 与 `~` 在 MySQL(位运算)和 PostgreSQL(`^` 为幂、
1106
+ > `~` 为正则匹配)中含义冲突,本实现统一采用 **MySQL 语义**:`^` = 按位异或、
1107
+ > `~` = 按位取反。需要幂运算请用 `POWER(x, n)`,需要正则请用 `REGEXP` / `~*`。
1108
+ > 因此 PG 风格的 `^`(幂)、`~ ~* !~ !~*`(正则)与 JSON 操作符
1109
+ > (`-> ->> #> #>> @> <@`)**暂不支持**。
1103
1110
 
1104
1111
  **JSON (PG style)** — `->` (JSON result), `->>` (text), `#>`, `#>>` (paths), `@>` `<@`
1105
1112
  (containment), `?` `?|` `?&` (key existence).
@@ -1457,7 +1464,7 @@ jsql mod --engine wasm # switch engine (restart required)
1457
1464
 
1458
1465
  ```bash
1459
1466
  $ jsql version
1460
- jsql-neo v5.6.0
1467
+ jsql-neo v6.0.1
1461
1468
  engine: native (napi) | wasm | js
1462
1469
  node: v22.0.0 platform: linux x64
1463
1470
  ```
@@ -1478,7 +1485,7 @@ jsql tui --memory -q # memory mode, quiet
1478
1485
  jsql tui --prompt 'db> ' --no-color
1479
1486
  ```
1480
1487
 
1481
- The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=5.6.0`.
1488
+ The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=6.0.1`.
1482
1489
 
1483
1490
  ### Keyboard shortcuts
1484
1491
 
@@ -1753,6 +1760,105 @@ normalization on data dirs and import/export paths; Web UI db-name whitelist (no
1753
1760
  }
1754
1761
  ```
1755
1762
 
1763
+ ## Plugin System
1764
+
1765
+ JSQL-NEO ships with a unified plugin system that works across all three engine implementations:
1766
+ **Native** (`NativeJSQL`), **WASM** (`JSQL`), and **Pure JS** (`Database`). Full documentation
1767
+ is in [PLUGINS.md](./PLUGINS.md).
1768
+
1769
+ ### Quick start
1770
+
1771
+ ```js
1772
+ const { Database, Plugin } = require('jsql-neo');
1773
+ const db = new Database();
1774
+
1775
+ db.use({
1776
+ name: 'my-plugin',
1777
+ install(db, ctx) {
1778
+ db.myFlag = true;
1779
+ },
1780
+ hooks: {
1781
+ beforeInsert(table, data) { /* modify data */ return data; },
1782
+ afterInsert(table, data, ids) {},
1783
+ beforeUpdate(table, id, data) { return true; }, // false = abort
1784
+ afterUpdate(table, id, data, result) {},
1785
+ beforeDelete(table, id) { return true; },
1786
+ afterDelete(table, id, result) {},
1787
+ beforeFind(table, query) { return true; },
1788
+ afterFind(table, query, result) {},
1789
+ },
1790
+ });
1791
+ ```
1792
+
1793
+ ### Hooks reference (18 unified hooks)
1794
+
1795
+ | Hook | Parameters | Return | Description |
1796
+ |---|---|---|---|
1797
+ | `beforeInsert` | `(table, data[])` | modified data / `false` to abort | pre-insert |
1798
+ | `afterInsert` | `(table, data[], ids)` | — | post-insert |
1799
+ | `beforeUpdate` | `(table, id, data)` | `true`/`false` | pre-update |
1800
+ | `afterUpdate` | `(table, id, data, result)` | — | post-update |
1801
+ | `beforeDelete` | `(table, id)` | `true`/`false` | pre-delete |
1802
+ | `afterDelete` | `(table, id, result)` | — | post-delete |
1803
+ | `beforeFind` | `(table, query)` | `true`/`false` | pre-query |
1804
+ | `afterFind` | `(table, query, result)` | — | post-query |
1805
+ | `beforeCreateTable` | `(name, schema)` | `true`/`false` | pre-DDL |
1806
+ | `afterCreateTable` | `(name, schema)` | — | post-DDL |
1807
+ | `beforeDropTable` | `(name)` | `true`/`false` | pre-drop |
1808
+ | `afterDropTable` | `(name)` | — | post-drop |
1809
+ | `beforeFlush` | `()` | `true`/`false` | pre-flush |
1810
+ | `afterFlush` | `()` | — | post-flush |
1811
+ | `beforeCount` | `(table)` | `true`/`false` | pre-count |
1812
+ | `afterCount` | `(table, count)` | — | post-count |
1813
+ | `onStart` | `()` | — | engine start |
1814
+ | `onStop` | `()` | — | engine stop |
1815
+
1816
+ ### Events
1817
+
1818
+ | Event | Payload |
1819
+ |---|---|
1820
+ | `insert` | `{ table, count, ids }` |
1821
+ | `update` | `{ table, id, data }` |
1822
+ | `delete` | `{ table, ids }` |
1823
+ | `createTable` | `{ name, schema }` |
1824
+ | `dropTable` | `{ name }` |
1825
+ | `start` / `stop` | `{}` |
1826
+
1827
+ ### Plugin helper class
1828
+
1829
+ ```js
1830
+ const timestampPlugin = new Plugin('timestamp')
1831
+ .on('beforeInsert', (table, data) => {
1832
+ data.forEach(r => r.created_at = new Date().toISOString());
1833
+ return data;
1834
+ })
1835
+ .onEvent((ev, payload) => console.log(ev))
1836
+ .install((db, ctx) => {})
1837
+ .build();
1838
+
1839
+ db.use(timestampPlugin);
1840
+ ```
1841
+
1842
+ ### Module system (CLI)
1843
+
1844
+ ```bash
1845
+ jsql mod add --address /path/mod.js # register module
1846
+ jsql mod enable <name> # enable (auto-enables dependencies)
1847
+ jsql mod disable <name> # disable (cascades)
1848
+ jsql mod list | ls [--json] # list
1849
+ ```
1850
+
1851
+ Modules persist to `~/.config/jsql/mod.config`. Circular dependencies are rejected;
1852
+ disabling cascades to dependents.
1853
+
1854
+ > **插件没有沙箱,请只装可信来源的模块。** 两条装载路径都不做隔离:
1855
+ > - `db.use(plugin)` —— 直接把插件对象挂进核心,与核心同权;
1856
+ > - `jsql mod add <dir>` —— 模块文件走普通 `require()`,同样与核心同权,
1857
+ > 可访问 `fs` / `child_process` 等一切 Node API。
1858
+ >
1859
+ > 早期文档曾写"模块文件在 VM 沙箱中执行",实际代码中不存在 vm 隔离,此处已更正。
1860
+ > 需要隔离时请在进程级解决(容器 / 子进程 / 单独的用户与权限)。
1861
+
1756
1862
  ---
1757
1863
 
1758
1864
  ## TypeScript
@@ -2378,7 +2484,7 @@ createMysqlServer({
2378
2484
 
2379
2485
  支持常见系统查询,让 ORM 与 GUI 工具的初始化探测流程正常工作:
2380
2486
 
2381
- - `SELECT VERSION()` → `8.0.0-jsql-neo`
2487
+ - `SELECT VERSION()` → `8.0.1-jsql-neo`
2382
2488
  - `SELECT @@version` / `@@version_comment` 等系统变量
2383
2489
  - `SHOW DATABASES` / `SHOW TABLES` / `SHOW COLUMNS FROM t` / `SHOW CREATE TABLE t`
2384
2490
  - `information_schema.tables` / `information_schema.columns` / `information_schema.statistics`
@@ -2410,7 +2516,7 @@ SELECT TABLE_NAME, TABLE_ROWS FROM information_schema.tables WHERE TABLE_SCHEMA
2410
2516
 
2411
2517
  | 函数 | 行为 |
2412
2518
  |---|---|
2413
- | `VERSION()` | `8.0.0-jsql-neo` |
2519
+ | `VERSION()` | `8.0.1-jsql-neo` |
2414
2520
  | `NOW()` / `CURRENT_TIMESTAMP` | `YYYY-MM-DD HH:MM:SS`(UTC) |
2415
2521
  | `CONCAT(a, b, ...)` | 字符串拼接 |
2416
2522
  | `GROUP_CONCAT(x)` | 聚合拼接(逗号分隔) |
@@ -3104,26 +3210,28 @@ SELECT * FROM orders LIMIT 40 OFFSET 20; -- 标准偏移语法
3104
3210
  | `BEGIN` / `START TRANSACTION` | 开启事务 |
3105
3211
  | `COMMIT` | 提交 |
3106
3212
  | `ROLLBACK` | 回滚 |
3107
- | `SAVEPOINT sp` / `ROLLBACK TO SAVEPOINT sp` / `RELEASE SAVEPOINT sp` | 保存点 |
3213
+ | `SAVEPOINT sp` / `RELEASE SAVEPOINT sp` | 保存点(**仅登记**,无快照能力) |
3214
+ | `ROLLBACK TO SAVEPOINT sp` | **不支持**——会抛错,不会静默"假成功" |
3108
3215
  | 自动事务 | 无显式事务时单语句原子 |
3109
3216
 
3110
3217
  事务保证:
3111
3218
 
3112
3219
  - 读已提交(read committed)隔离级别的等价语义
3113
3220
  - 事务内修改对事务外不可见(提交前),提交后原子可见
3114
- - 回滚撤销本事务全部修改(含保存点回滚)
3221
+ - 回滚撤销本事务全部修改
3115
3222
  - 支持跨表事务(引擎级提交日志)
3116
3223
 
3117
3224
  ```sql
3118
3225
  BEGIN;
3119
3226
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
3120
3227
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
3121
- SAVEPOINT sp1;
3122
- UPDATE accounts SET balance = balance - 100 WHERE id = 3;
3123
- ROLLBACK TO SAVEPOINT sp1; -- 撤销 id=3 的修改
3124
3228
  COMMIT;
3125
3229
  ```
3126
3230
 
3231
+ > **`ROLLBACK TO SAVEPOINT` 目前不可用。** 引擎没有保存点/快照能力,无法只回退到某个
3232
+ > 保存点。这里刻意选择"抛错"而不是"静默成功"——假装回滚成功会让调用方误以为数据已还原。
3233
+ > 需要部分回滚时请用 `ROLLBACK` 放弃整个事务。
3234
+
3127
3235
  #### 其他语句
3128
3236
 
3129
3237
  | 语句 | 说明 |
@@ -3245,7 +3353,7 @@ INTERVAL 单位:`DAY` `HOUR` `MINUTE` `SECOND` `WEEK` `MONTH` `YEAR` 及其组
3245
3353
 
3246
3354
  | 函数 | 说明 | 示例 |
3247
3355
  |---|---|---|
3248
- | `VERSION()` | 服务器版本 | `8.0.0-jsql-neo` |
3356
+ | `VERSION()` | 服务器版本 | `8.0.1-jsql-neo` |
3249
3357
  | `DATABASE()` | 当前库名 | `DATABASE()` |
3250
3358
  | `USER()` / `CURRENT_USER` | 当前用户 | `USER()` → `root` |
3251
3359
  | `LAST_INSERT_ID()` | 最近自增 ID | 配合 AUTO_INCREMENT 使用 |
@@ -4042,7 +4150,7 @@ Data dir: /root/.jsql-neo/data
4042
4150
 
4043
4151
  ```bash
4044
4152
  $ jsql version
4045
- jsql-neo v5.6.0
4153
+ jsql-neo v6.0.1
4046
4154
  engine: native (napi) | wasm | js
4047
4155
  node: v22.0.0
4048
4156
  platform: linux x64
@@ -4711,7 +4819,7 @@ jsql-neo/
4711
4819
  ├── index.js # ★ 公共 API 出口
4712
4820
  ├── index.d.ts # TypeScript 声明
4713
4821
  ├── test/ # 测试套件
4714
- ├── docs/ # 文档(本文件即索引)
4822
+ ├── PLUGINS.md # 插件与模块文档(与 README 同级,无 docs/ 目录)
4715
4823
  ├── src/rust/ # Rust 引擎源码(native + wasm)
4716
4824
  ├── data/ # 默认数据目录(运行时创建)
4717
4825
  └── package.json
@@ -4902,7 +5010,7 @@ npm run typecheck
4902
5010
  - `src/rust/`:Rust 引擎(native + wasm 同源)
4903
5011
  - `lib/`:Node 侧全部逻辑(协议/解析/TUI/兼容层)
4904
5012
  - `test/`:单元 + 集成测试
4905
- - `docs/`:扩展文档
5013
+ - 扩展文档位于仓库根目录:`PLUGINS.md`(插件与模块)。本仓库**没有** `docs/` 目录。
4906
5014
 
4907
5015
  ---
4908
5016
 
@@ -5119,7 +5227,7 @@ Apache License
5119
5227
 
5120
5228
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
5121
5229
 
5122
- *文档版本:v5.6.0 · 最后更新:2026-08-12*
5230
+ *文档版本:v6.0.1 · 最后更新:2026-09-27*
5123
5231
 
5124
5232
  ---
5125
5233
 
@@ -6594,7 +6702,7 @@ FROM orders;
6594
6702
  #### 会话信息
6595
6703
 
6596
6704
  ```sql
6597
- SELECT VERSION(); -- '8.0.0-jsql-neo'
6705
+ SELECT VERSION(); -- '8.0.1-jsql-neo'
6598
6706
  SELECT DATABASE(); -- 'app'
6599
6707
  SELECT USER(); -- 'root@localhost'
6600
6708
  SELECT CURRENT_USER; -- 'root@localhost'
@@ -6889,7 +6997,7 @@ Usage: jsql version
6889
6997
 
6890
6998
  输出版本与环境信息:
6891
6999
 
6892
- jsql-neo v5.6.0
7000
+ jsql-neo v6.0.1
6893
7001
  engine: native (napi) | wasm | js
6894
7002
  node: v22.0.0
6895
7003
  platform: linux x64
@@ -7545,7 +7653,7 @@ CI(GitHub Actions)矩阵:`node 20/22` × `linux/macos/windows` × `native/
7545
7653
 
7546
7654
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
7547
7655
 
7548
- *文档版本:v5.4.0 · 共 19 个附录 · 最后更新:2026-08-12*
7656
+ *文档版本:v6.0.1 · 共 19 个附录 · 最后更新:2026-09-27*
7549
7657
 
7550
7658
  ---
7551
7659
 
@@ -8152,4 +8260,4 @@ npm test
8152
8260
 
8153
8261
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
8154
8262
 
8155
- *文档版本:v5.4.0 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-08-12*
8263
+ *文档版本:v6.0.1 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-09-27*
package/index.d.ts CHANGED
@@ -85,7 +85,7 @@ declare namespace JSQLNeo {
85
85
 
86
86
  export class JSQL {
87
87
  constructor(opts?: JSQLOptions);
88
- use(plugin: JSQLPlugin | ((db: JSQL) => void)): this;
88
+ use(plugin: JSQLPlugin | string | ((db: JSQL) => void), opts?: Record<string, unknown>): this;
89
89
  on(event: JSQLHook, fn: (...args: any[]) => unknown): this;
90
90
  onEvent(fn: (event: string, data: unknown) => void): this;
91
91
  start(): Promise<void>;
@@ -113,6 +113,15 @@ declare namespace JSQLNeo {
113
113
  * @returns 单条语句返回单个结果对象(含 columns/rows),多条返回数组。
114
114
  */
115
115
  query(sql: string, paramsOrOpts?: unknown[] | Record<string, unknown>, opts?: Record<string, unknown>): Promise<any>;
116
+ exists(table: string, filter?: Record<string, unknown>): Promise<boolean>;
117
+ prepare(sql: string): PreparedStatement;
118
+ }
119
+
120
+ export interface PreparedStatement {
121
+ sql: string;
122
+ all(...params: unknown[]): Promise<any>;
123
+ get(...params: unknown[]): Promise<Record<string, unknown> | null>;
124
+ run(...params: unknown[]): Promise<any>;
116
125
  }
117
126
 
118
127
  export class NativeJSQL extends JSQL {}
@@ -309,6 +318,9 @@ declare namespace JSQLNeo {
309
318
  createTable(name: string, schema: Schema): Promise<unknown>;
310
319
  insert(table: string, data: Record<string, unknown>): Promise<unknown>;
311
320
  find(table: string, filter?: Record<string, unknown>): Promise<Row[]>;
321
+ exists(table: string, filter?: Record<string, unknown>): boolean;
322
+ prepare(sql: string): PreparedStatement;
323
+ query(sql: string, paramsOrOpts?: unknown[] | SQLOptions, opts?: SQLOptions): Promise<SQLResult>;
312
324
  update(table: string, filter: Record<string, unknown>, data: Record<string, unknown>): Promise<number>;
313
325
  remove(table: string, filter: Record<string, unknown>): Promise<number>;
314
326
  findOne(table: string, filter?: Record<string, unknown>): Promise<Row | null>;
@@ -316,12 +328,14 @@ declare namespace JSQLNeo {
316
328
  getTables(): Promise<string[]>;
317
329
  dropTable(name: string): Promise<void>;
318
330
  getTableSchema(name: string): Promise<Schema | null>;
331
+ use(plugin: JSQLPlugin | string | ((db: Database) => void), opts?: Record<string, unknown>): this;
319
332
  }
320
333
 
321
334
  export class Table {
322
335
  constructor(name: string, schema: Schema, db?: unknown);
323
336
  insert(data: Record<string, unknown>): Promise<unknown>;
324
337
  find(filter?: Record<string, unknown>): Promise<Row[]>;
338
+ exists(filter?: Record<string, unknown>): boolean;
325
339
  updateById(id: number | string, data: Record<string, unknown>): Promise<unknown>;
326
340
  removeById(id: number | string): Promise<unknown>;
327
341
  count(): Promise<number>;
@@ -329,15 +343,27 @@ declare namespace JSQLNeo {
329
343
 
330
344
  export class Query {
331
345
  constructor(table: unknown);
346
+ where(conditions: Record<string, unknown>): this;
347
+ orderBy(field: string, dir?: 'asc' | 'desc'): this;
348
+ limit(n: number): this;
349
+ offset(n: number): this;
350
+ get(): Row[];
351
+ first(): Row | null;
352
+ exists(): boolean;
353
+ count(): number;
332
354
  exec(): Promise<Row[]>;
333
355
  then(resolve: (rows: Row[]) => unknown, reject?: (err: Error) => unknown): Promise<unknown>;
334
356
  }
335
357
 
336
358
  export class BTree {
337
- constructor(order?: number);
359
+ constructor(order?: number, unique?: boolean);
338
360
  insert(key: unknown, value: unknown): void;
339
- find(key: unknown): unknown;
340
- range(min: unknown, max: unknown): unknown[];
361
+ search(key: unknown): number[];
362
+ searchMany(keys: unknown[]): number[];
363
+ prefix(prefix: string): number[];
364
+ range(min: unknown, max: unknown): number[];
365
+ greaterThan(min: unknown): number[];
366
+ lessThan(max: unknown): number[];
341
367
  }
342
368
 
343
369
  export class Cache {
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * JSQL-NEO v6.0.0-beta1 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.0.0 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
package/lib/btree.js CHANGED
@@ -50,20 +50,27 @@ class BTree {
50
50
  this._size++;
51
51
  }
52
52
 
53
+ _lowerBound(keys, key) {
54
+ let lo = 0, hi = keys.length;
55
+ while (lo < hi) {
56
+ const mid = (lo + hi) >> 1;
57
+ if (keys[mid] < key) lo = mid + 1;
58
+ else hi = mid;
59
+ }
60
+ return lo;
61
+ }
62
+
53
63
  _insertNonFull(node, key, rowIndex) {
54
64
  if (node.leaf) {
55
- // 查找插入位置
56
- let i = node.keys.length - 1;
57
- while (i >= 0 && key < node.keys[i]) i--;
58
- i++;
65
+ const i = this._lowerBound(node.keys, key);
59
66
 
60
- if (i > 0 && node.keys[i - 1] === key) {
67
+ if (i < node.keys.length && node.keys[i] === key) {
61
68
  if (this._unique) {
62
69
  this._size--;
63
70
  return;
64
71
  }
65
- if (!node.values[i - 1].includes(rowIndex)) {
66
- node.values[i - 1].push(rowIndex);
72
+ if (!node.values[i].includes(rowIndex)) {
73
+ node.values[i].push(rowIndex);
67
74
  }
68
75
  this._size--;
69
76
  return;
@@ -119,8 +126,7 @@ class BTree {
119
126
  * (分隔键副本可能在左子树作为最大值,或在右子树作为最小值)。
120
127
  */
121
128
  _route(node, key) {
122
- let i = 0;
123
- while (i < node.keys.length && key > node.keys[i]) i++;
129
+ let i = this._lowerBound(node.keys, key);
124
130
  if (i < node.keys.length && node.keys[i] === key) {
125
131
  return this._getMax(node.children[i]).key === key ? i : i + 1;
126
132
  }
@@ -134,8 +140,43 @@ class BTree {
134
140
  search(key) {
135
141
  let node = this._root;
136
142
  while (!node.leaf) node = node.children[this._route(node, key)];
137
- const i = node.keys.indexOf(key);
138
- return i === -1 ? [] : node.values[i];
143
+ const i = this._lowerBound(node.keys, key);
144
+ return (i < node.keys.length && node.keys[i] === key) ? node.values[i] : [];
145
+ }
146
+
147
+ searchMany(keys) {
148
+ if (!keys || keys.length === 0) return [];
149
+ const out = [];
150
+ const seen = new Set();
151
+ for (let i = 0; i < keys.length; i++) {
152
+ const hits = this.search(keys[i]);
153
+ for (let j = 0; j < hits.length; j++) {
154
+ const idx = hits[j];
155
+ if (!seen.has(idx)) {
156
+ seen.add(idx);
157
+ out.push(idx);
158
+ }
159
+ }
160
+ }
161
+ return out;
162
+ }
163
+
164
+ prefix(prefix) {
165
+ if (prefix === undefined || prefix === null) return [];
166
+ const p = String(prefix);
167
+ const result = [];
168
+ let node = this._findLeaf(p);
169
+ while (node) {
170
+ for (let i = 0; i < node.keys.length; i++) {
171
+ const k = node.keys[i];
172
+ const s = k === undefined || k === null ? '' : String(k);
173
+ if (s < p) continue;
174
+ if (!s.startsWith(p)) return result;
175
+ result.push(...node.values[i]);
176
+ }
177
+ node = node.next;
178
+ }
179
+ return result;
139
180
  }
140
181
 
141
182
  /**
@@ -173,6 +214,18 @@ class BTree {
173
214
  return result;
174
215
  }
175
216
 
217
+ greaterThanEqual(min) {
218
+ const result = [];
219
+ let node = this._findLeaf(min);
220
+ while (node) {
221
+ for (let i = 0; i < node.keys.length; i++) {
222
+ if (node.keys[i] >= min) result.push(...node.values[i]);
223
+ }
224
+ node = node.next;
225
+ }
226
+ return result;
227
+ }
228
+
176
229
  /**
177
230
  * 小于查询(严格开区间,不含 max 本身)
178
231
  */
@@ -190,12 +243,24 @@ class BTree {
190
243
  return result;
191
244
  }
192
245
 
246
+ lessThanEqual(max) {
247
+ const result = [];
248
+ let node = this._root;
249
+ while (!node.leaf) node = node.children[0];
250
+ while (node) {
251
+ for (let i = 0; i < node.keys.length; i++) {
252
+ if (node.keys[i] <= max) result.push(...node.values[i]);
253
+ else return result;
254
+ }
255
+ node = node.next;
256
+ }
257
+ return result;
258
+ }
259
+
193
260
  _findLeaf(key) {
194
261
  let node = this._root;
195
262
  while (!node.leaf) {
196
- let i = 0;
197
- while (i < node.keys.length && key > node.keys[i]) i++;
198
- node = node.children[i];
263
+ node = node.children[this._lowerBound(node.keys, key)];
199
264
  }
200
265
  return node;
201
266
  }
@@ -216,8 +281,8 @@ class BTree {
216
281
 
217
282
  _removeFromNode(node, key, rowIndex) {
218
283
  if (node.leaf) {
219
- const i = node.keys.indexOf(key);
220
- if (i === -1) return false;
284
+ const i = this._lowerBound(node.keys, key);
285
+ if (i >= node.keys.length || node.keys[i] !== key) return false;
221
286
  if (!node.values[i].includes(rowIndex)) return false;
222
287
  const vals = node.values[i].filter(v => v !== rowIndex);
223
288
  if (vals.length === 0) {
package/lib/database.js CHANGED
@@ -435,6 +435,17 @@ class Database {
435
435
  if (!name || name.length > 64 || name === '.' || name === '..' || /[\x00-\x1f/\\]|\.\./.test(name)) {
436
436
  throw createError('ER_BAD_TABLE_NAME', name);
437
437
  }
438
+ if (typeof schema === 'string') {
439
+ const parsed = {};
440
+ for (const seg of schema.split(',')) {
441
+ const trimmed = seg.trim();
442
+ if (!trimmed) continue;
443
+ const parts = trimmed.split(/\s+/);
444
+ const field = parts.shift();
445
+ parsed[field] = parts.join(' ').toLowerCase().trim() || 'string';
446
+ }
447
+ schema = parsed;
448
+ }
438
449
  if (!this._runHooks('beforeCreateTable', [name, schema])) throw createError('ER_PLUGIN_ABORT', 'createTable aborted by plugin');
439
450
  if (this._tables[name] || (this._dirMode && this._meta.tables[name])) {
440
451
  throw createError('ER_TABLE_EXISTS_ERROR', name);
@@ -1076,10 +1087,11 @@ class Database {
1076
1087
 
1077
1088
  /**
1078
1089
  * 注册插件
1079
- * @param {object|function} plugin - { name, version, install(db, ctx), hooks, api, onEvent } 或函数 (作为 install)
1090
+ * @param {object|function|string} plugin - { name, version, install(db, ctx), hooks, api, onEvent }、函数或内置插件名
1091
+ * @param {object} [opts] - 传给内置插件工厂的配置,例如 db.use('timestamps', { createdField: 'created_at' })
1080
1092
  */
1081
- use(plugin) {
1082
- return applyPlugin(this, plugin);
1093
+ use(plugin, opts) {
1094
+ return applyPlugin(this, plugin, opts);
1083
1095
  }
1084
1096
 
1085
1097
  _buildCtx(plugin) {
@@ -1212,7 +1224,7 @@ class Database {
1212
1224
  if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
1213
1225
  if (!this._runHooks('beforeFind', [tableName, { filter, opts }])) return [];
1214
1226
  const { limit, offset = 0 } = opts;
1215
- let rows = table._applyFilter(table._rows, filter || {});
1227
+ let rows = table._applyFilterOptimized(table._rows, filter || {});
1216
1228
  if (limit > 0) rows = rows.slice(offset, offset + limit);
1217
1229
  else if (offset > 0) rows = rows.slice(offset);
1218
1230
  this._touchTable(tableName);
@@ -1220,6 +1232,25 @@ class Database {
1220
1232
  return rows;
1221
1233
  }
1222
1234
 
1235
+ exists(tableName, filter = {}) {
1236
+ const table = this._ensureTable(tableName);
1237
+ if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
1238
+ this._touchTable(tableName);
1239
+ return table.exists(filter);
1240
+ }
1241
+
1242
+ async query(sql, paramsOrOpts, opts) {
1243
+ const { executeSQL } = require('./sql');
1244
+ if (!this._runHooks('beforeQuery', [sql, paramsOrOpts, opts])) return null;
1245
+ const result = await executeSQL(this, sql, paramsOrOpts, opts);
1246
+ this._runHooks('afterQuery', [sql, paramsOrOpts, opts, result]);
1247
+ return result;
1248
+ }
1249
+
1250
+ prepare(sql) {
1251
+ return require('./prepared').createPrepared(this, sql);
1252
+ }
1253
+
1223
1254
  findById(tableName, id) {
1224
1255
  const table = this._ensureTable(tableName);
1225
1256
  if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
@@ -322,7 +322,7 @@ class MongoServer {
322
322
  localTime: new Date(), logicalSessionTimeoutMinutes: 30, connectionId: 1,
323
323
  };
324
324
  case 'ping': return { ok: 1 };
325
- case 'buildInfo': return { ok: 1, version: '5.6.0-jsql-neo', gitVersion: 'jsql-neo', versionArray: [5, 6, 0, 0] };
325
+ case 'buildInfo': return { ok: 1, version: '6.0.0-jsql-neo', gitVersion: 'jsql-neo', versionArray: [6, 0, 0, 0] };
326
326
  case 'getParameter': return { ok: 1 };
327
327
  case 'endSessions': return { ok: 1 };
328
328
  case 'listDatabases':
@@ -499,11 +499,15 @@ class JSQL {
499
499
  this._buffer[table].push(arr[0]);
500
500
  this._bufferSize++;
501
501
  const flushed = await this._flush();
502
- return flushed && flushed[table] ? flushed[table] : null;
502
+ const result = flushed && flushed[table] ? flushed[table] : null;
503
+ this._emit('insert', { table, count: arr.length, ids: result });
504
+ this._runHooks('afterInsert', [table, filtered, result]);
505
+ return result;
503
506
  }
504
507
 
505
508
  async createTable(name, schema) {
506
509
  await this._flush();
510
+ if (typeof schema === 'string') schema = parseSchemaString(schema);
507
511
  if (!this._runHooks('beforeCreateTable', [name, schema])) return null;
508
512
  const r = safeParse(native.jsqlCreateTable(name, JSON.stringify(mapNativeSchema(schema))));
509
513
  if (r && r.ok === false) throw new Error(r.error || 'create table failed');
@@ -656,6 +660,15 @@ class JSQL {
656
660
  return result;
657
661
  }
658
662
 
663
+ async exists(table, filter = {}) {
664
+ const rows = await this.find(table, filter, { limit: 1 });
665
+ return Array.isArray(rows) && rows.length > 0;
666
+ }
667
+
668
+ prepare(sql) {
669
+ return require('./prepared').createPrepared(this, sql);
670
+ }
671
+
659
672
  async stop() {
660
673
  this._flushOpsNow();
661
674
  await this._flush();