jsql-neo 6.5.11 → 6.5.13

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.5.11** — stable release · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
6
+ > **v6.5.13** — 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
  ![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)
@@ -16,6 +16,17 @@
16
16
 
17
17
  ---
18
18
 
19
+ ## Demo
20
+
21
+ ![JSQL-NEO terminal demo](doc/demo.gif)
22
+
23
+ A real `jsql tui` session: joins, NULL semantics, exact 64-bit integers, and
24
+ savepoint rollback.
25
+
26
+ [MP4 version](doc/demo.mp4) · [asciinema recording](doc/demo.cast)
27
+
28
+ ---
29
+
19
30
  ## 🚀 Quick Start
20
31
 
21
32
  ### One-line install
@@ -160,6 +171,33 @@ await db.start();
160
171
 
161
172
  ---
162
173
 
174
+ ## ✨ XQL 集成
175
+
176
+ 用 [XQL](https://github.com/vexify-org/XQL) 查询 JSQL 表:把 SQL 直接接到文档上,
177
+ 支持等值 hash 索引与范围 range 索引,速度不低于手写 JS 循环,并支持管道式语法。
178
+
179
+ ```js
180
+ const { NativeJSQL } = require('jsql-neo');
181
+ const db = new NativeJSQL();
182
+ await db.start();
183
+
184
+ db.use(require('jsql-neo/lib/plugins').xql());
185
+
186
+ // 标准 SQL
187
+ const rows = await db.xql('SELECT name FROM users WHERE age > 18 ORDER BY name');
188
+ // 管道式(同样的语义)
189
+ const pipe = await db.xql('FROM users |> WHERE age > 18 |> SELECT name |> ORDER BY name');
190
+
191
+ // 索引:等值 → hash 索引 O(1),范围 → 有序索引 O(log n + k)
192
+ db.xqlIndex('users', 'id');
193
+ db.xqlIndex('users', 'score', 'range');
194
+ ```
195
+
196
+ 写入(INSERT / UPDATE / DELETE)后 XQL 缓存自动失效、下次查询重建;XQL 只读,
197
+ 不做写回——数据的唯一权威仍是 JSQL 引擎。详见 [PLUGINS.md](PLUGINS.md#xql-插件)。
198
+
199
+ ---
200
+
163
201
  ## 📖 Documentation
164
202
 
165
203
  Detailed docs live in `doc/` — click to read.
package/index.d.ts CHANGED
@@ -115,6 +115,22 @@ declare namespace JSQLNeo {
115
115
  query(sql: string, paramsOrOpts?: unknown[] | Record<string, unknown>, opts?: Record<string, unknown>): Promise<any>;
116
116
  exists(table: string, filter?: Record<string, unknown>): Promise<boolean>;
117
117
  prepare(sql: string): PreparedStatement;
118
+
119
+ // ── xql 插件(db.use('xql') 后可用)─────────────────────
120
+ /** 用 XQL 查询(自动加载源表)。opts.table 可显式指定数据源。 */
121
+ xql(sql: string, opts?: { table?: string }): Promise<Row[]>;
122
+ /** 显式加载一张表成 XQL 集合 */
123
+ xqlLoad(table: string): Promise<unknown>;
124
+ /** 强制重建某表的 XQL 集合 */
125
+ xqlRefresh(table: string): Promise<unknown>;
126
+ /** 运行时建索引(type 缺省按字段类型猜,'range' 为有序索引) */
127
+ xqlIndex(table: string, field: string, type?: 'hash' | 'range'): this;
128
+ /** 已加载的 XQL 集合名 */
129
+ xqlTables(): string[];
130
+ /** 取某表的 XQL 集合(未加载返回 null) */
131
+ xqlCollection(table: string): unknown | null;
132
+ /** 清缓存(不传清全部) */
133
+ xqlClear(table?: string): this;
118
134
  }
119
135
 
120
136
  export interface PreparedStatement {
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * JSQL-NEO v6.5.11 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.5.13 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
package/lib/database.js CHANGED
@@ -1668,12 +1668,19 @@ class Database {
1668
1668
  const pluginName = manifest.name || entry.name;
1669
1669
  // 查找入口文件:manifest.main || manifest.entry || 'index.js'
1670
1670
  const entryFile = manifest.main || manifest.entry || 'index.js';
1671
- // 兼容两种路径:根级 + plugins/ 子目录
1671
+ // 兼容三种布局:
1672
+ // 1. entry 是仓库相对路径(如 "plugins/xql/index.js")且文件在根级 → basename
1673
+ // 2. entry 是根级相对路径(如 "index.js")且文件在根级
1674
+ // 3. plugins/ 子目录(旧布局)
1672
1675
  const candidates = [
1673
1676
  path.join(dir, entryFile),
1674
1677
  path.join(dir, entryFile + '.js'),
1678
+ path.join(dir, path.basename(entryFile)),
1679
+ path.join(dir, path.basename(entryFile) + '.js'),
1675
1680
  path.join(dir, 'plugins', entryFile),
1676
1681
  path.join(dir, 'plugins', entryFile + '.js'),
1682
+ path.join(dir, 'plugins', path.basename(entryFile)),
1683
+ path.join(dir, 'plugins', path.basename(entryFile) + '.js'),
1677
1684
  ];
1678
1685
  let entryPath = null;
1679
1686
  for (const c of candidates) {
@@ -16,6 +16,7 @@ const { createLogger } = require('./logger');
16
16
  const { createMetrics } = require('./metrics');
17
17
  const { createAudit } = require('./audit');
18
18
  const { createValidation } = require('./validation');
19
+ const { createXql } = require('./xql');
19
20
 
20
21
  /** 名称 → 插件工厂 */
21
22
  const builtins = {
@@ -23,7 +24,8 @@ const builtins = {
23
24
  logger: createLogger,
24
25
  metrics: createMetrics,
25
26
  audit: createAudit,
26
- validation: createValidation
27
+ validation: createValidation,
28
+ xql: createXql
27
29
  };
28
30
 
29
31
  /** 内置插件名清单 */
@@ -60,11 +62,13 @@ module.exports = {
60
62
  createMetrics,
61
63
  createAudit,
62
64
  createValidation,
65
+ createXql,
63
66
  timestamps: createTimestamps,
64
67
  logger: createLogger,
65
68
  metrics: createMetrics,
66
69
  audit: createAudit,
67
70
  validation: createValidation,
71
+ xql: createXql,
68
72
  builtins,
69
73
  list,
70
74
  create,
@@ -0,0 +1,163 @@
1
+ /**
2
+ * xql —— 用 XQL(eXtend Query Language)查询 JSQL 表
3
+ *
4
+ * XQL 把 SQL 直接接到 JS 文档上:支持等值 hash 索引与范围 range 索引,
5
+ * 查询速度不低于手写 JS 循环,并支持管道式语法(FROM t |> WHERE … |> LIMIT n)。
6
+ *
7
+ * 本插件把 JSQL 表加载成 XQL 集合(自动分页读全表),写入时标记失效、
8
+ * 下次查询重建,从而在 JSQL 之上获得 XQL 的管道语法与索引。XQL 只读,
9
+ * 不做写回——数据的唯一权威仍然是 JSQL 引擎本身。
10
+ *
11
+ * @example
12
+ * db.use(require('jsql-neo/lib/plugins').xql());
13
+ *
14
+ * await db.xql('SELECT name FROM users WHERE age > 18 ORDER BY name');
15
+ * await db.xql('FROM users |> WHERE age > 18 |> SELECT name |> ORDER BY name');
16
+ * db.xqlIndex('users', 'id'); // 等值 → hash 索引,O(1)
17
+ * db.xqlIndex('users', 'score', 'range'); // 范围 → range 索引
18
+ *
19
+ * @example 自动建索引 + 显式加载
20
+ * db.use(require('jsql-neo/lib/plugins').xql({
21
+ * indexes: { users: ['id', ['score', 'range']] },
22
+ * pageSize: 2000,
23
+ * }));
24
+ * await db.xqlLoad('users');
25
+ */
26
+ const { definePlugin } = require('../plugin');
27
+
28
+ /** 规整索引配置:'id' | ['score','range'] → ['id', null] | ['score','range'] */
29
+ function normalizeIndexSpecs(specs) {
30
+ return (specs || []).map(s => Array.isArray(s) ? [s[0], s[1] || null] : [s, null]);
31
+ }
32
+
33
+ /** 记录索引规格(去重,按字段名) */
34
+ function recordIndexSpec(state, table, field, type) {
35
+ const list = state.indexSpecs[table] || (state.indexSpecs[table] = []);
36
+ const exists = list.some(s => (Array.isArray(s) ? s[0] : s) === field);
37
+ if (!exists) list.push(type ? [field, type] : field);
38
+ }
39
+
40
+ function createXql(opts = {}) {
41
+ const pageSize = opts.pageSize || 1000;
42
+ const autoLoad = opts.autoLoad !== false;
43
+ const getXql = opts.xql || (() => require('../xql'));
44
+
45
+ return definePlugin('xql', {
46
+ config: { pageSize, autoLoad, indexes: opts.indexes || {} },
47
+ install(engine, ctx) {
48
+ const state = {
49
+ collections: new Map(), // table -> Collection
50
+ dirty: new Set(), // table 需要重建
51
+ indexSpecs: Object.assign({}, opts.indexes || {}),
52
+ };
53
+ ctx.store.set('xql', state);
54
+
55
+ const load = async (table) => {
56
+ const rows = [];
57
+ let offset = 0;
58
+ for (;;) {
59
+ const batch = await engine.find(table, {}, { limit: pageSize, offset });
60
+ if (!Array.isArray(batch) || batch.length === 0) break;
61
+ rows.push(...batch);
62
+ if (batch.length < pageSize) break;
63
+ offset += batch.length;
64
+ }
65
+ const xql = getXql();
66
+ const coll = new xql.Collection(table, rows);
67
+ for (const [field, type] of normalizeIndexSpecs(state.indexSpecs[table])) {
68
+ coll.index(field, type);
69
+ }
70
+ state.collections.set(table, coll);
71
+ state.dirty.delete(table);
72
+ return coll;
73
+ };
74
+
75
+ const ensure = async (table) => {
76
+ const has = state.collections.has(table);
77
+ if (!has && !autoLoad) {
78
+ throw new Error(`xql: 表 '${table}' 未加载 —— 先 db.xqlLoad('${table}')`);
79
+ }
80
+ if (!has || state.dirty.has(table)) return load(table);
81
+ return state.collections.get(table);
82
+ };
83
+
84
+ const invalidate = (table) => {
85
+ if (state.collections.has(table)) state.dirty.add(table);
86
+ };
87
+
88
+ // 写入 → 缓存失效(native 只有 beforeUpdate/beforeDelete 会触发,
89
+ // afterInsert 全引擎都有;用 before* 让三种引擎行为一致)
90
+ ctx.on('afterInsert', (t) => invalidate(t));
91
+ ctx.on('beforeUpdate', (t) => invalidate(t));
92
+ ctx.on('beforeDelete', (t) => invalidate(t));
93
+ ctx.on('afterDropTable', (t) => {
94
+ state.collections.delete(t);
95
+ state.dirty.delete(t);
96
+ });
97
+ ctx.on('onStop', () => {
98
+ state.collections.clear();
99
+ state.dirty.clear();
100
+ });
101
+
102
+ const api = {
103
+ version: getXql().version,
104
+
105
+ /** 用 XQL 查询(自动加载源表)。opts.table 可显式指定数据源。 */
106
+ async query(sql, opts = {}) {
107
+ const xql = getXql();
108
+ const src = opts.table || xql.compile(sql).__source;
109
+ const coll = await ensure(src);
110
+ return coll.query(sql);
111
+ },
112
+
113
+ /** 显式加载一张表成 XQL 集合(含配置的索引) */
114
+ load(table) { return load(table); },
115
+
116
+ /** 强制重建某张表的 XQL 集合 */
117
+ refresh(table) {
118
+ state.dirty.add(table);
119
+ return load(table);
120
+ },
121
+
122
+ /** 给表建索引(运行时)。等值不传 type → hash,传 'range' → 有序索引。 */
123
+ index(table, field, type) {
124
+ recordIndexSpec(state, table, field, type);
125
+ const c = state.collections.get(table);
126
+ if (c) c.index(field, type);
127
+ return engine;
128
+ },
129
+
130
+ /** 已加载的 XQL 集合名 */
131
+ tables() { return Array.from(state.collections.keys()); },
132
+
133
+ /** 取某表的 XQL 集合(未加载返回 null) */
134
+ collection(table) { return state.collections.get(table) || null; },
135
+
136
+ /** 清空缓存(可指定表;不传清全部) */
137
+ clear(table) {
138
+ if (table) {
139
+ state.collections.delete(table);
140
+ state.dirty.delete(table);
141
+ } else {
142
+ state.collections.clear();
143
+ state.dirty.clear();
144
+ }
145
+ return engine;
146
+ },
147
+ };
148
+
149
+ ctx.expose(api);
150
+
151
+ // 便捷方法直接挂到引擎上:db.xql / db.xqlIndex / …
152
+ engine.xql = api.query;
153
+ engine.xqlLoad = api.load;
154
+ engine.xqlRefresh = api.refresh;
155
+ engine.xqlIndex = api.index;
156
+ engine.xqlTables = api.tables;
157
+ engine.xqlCollection = api.collection;
158
+ engine.xqlClear = api.clear;
159
+ },
160
+ });
161
+ }
162
+
163
+ module.exports = { createXql, xql: createXql };
package/lib/xql/NOTICE ADDED
@@ -0,0 +1,16 @@
1
+ XQL — eXtend Query Language
2
+ Copyright 2026 Vexify
3
+
4
+ This product includes software developed at Vexify.
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
Binary file