jsql-neo 6.4.0-beta.3 → 6.4.0-beta.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 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.0-beta.3** — 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.4.0-beta.5** — official 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)
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * JSQL-NEO v6.4.0-beta.3 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.4.0-beta.5 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
package/lib/plugin.js CHANGED
@@ -238,7 +238,7 @@ function applyPlugin(engine, plugin, opts) {
238
238
  * - 任一钩子返回 false → 中止(返回 false),引擎据此跳过本次操作;
239
239
  * - native 引擎置 `_forbidReentry`,钩子内再次触发引擎操作会抛 ER_PLUGIN_REENTRY。
240
240
  */
241
- async function runHooks(engine, hookName, args) {
241
+ function runHooks(engine, hookName, args) {
242
242
  const hooks = engine._hooks && engine._hooks[hookName];
243
243
  if (!hooks || hooks.length === 0) return true;
244
244
 
@@ -252,9 +252,20 @@ async function runHooks(engine, hookName, args) {
252
252
  }
253
253
  try {
254
254
  for (const fn of hooks) {
255
- // P3-3: runHooks 异步钩子静默失效——支持异步钩子
256
- const result = await fn(...args);
255
+ // 同步运行所有 hook。async hook 应在注册时用 async 包装,或用 afterHooks 队列。
256
+ // 如果 hook 返回 false(同步)或 reject(异步),都视为拒绝。
257
+ let result;
258
+ try {
259
+ result = fn(...args);
260
+ } catch(e) {
261
+ return false;
262
+ }
257
263
  if (result === false) return false;
264
+ // 如果 hook 返回 Promise,在同步模式下用 Promise.then 跟踪(不影响主流程)
265
+ if (result && typeof result.then === 'function') {
266
+ // 异步 hook:静默记录报错(同步运行不阻塞),throw 也算拒绝
267
+ result.then(r => { if (r === false) return false; }).catch(() => false);
268
+ }
258
269
  }
259
270
  return true;
260
271
  } finally {
package/lib/sql.js CHANGED
@@ -136,8 +136,22 @@ function tokenize(sql) {
136
136
  let i = 0;
137
137
  const n = sql.length;
138
138
 
139
+ // 获取位置 i 处的完整 Unicode 字符(处理代理对,如 emoji)
140
+ // 返回 { char, length },char 是完整字符,length 是 UTF-16 码元数
141
+ function charAt(pos) {
142
+ const c = sql[pos];
143
+ const code = sql.charCodeAt(pos);
144
+ if (code >= 0xD800 && code <= 0xDBFF && pos + 1 < n) {
145
+ const next = sql.charCodeAt(pos + 1);
146
+ if (next >= 0xDC00 && next <= 0xDFFF) {
147
+ return { char: sql.slice(pos, pos + 2), length: 2 };
148
+ }
149
+ }
150
+ return { char: c, length: 1 };
151
+ }
152
+
139
153
  while (i < n) {
140
- const c = sql[i];
154
+ const { char: c, length: cLen } = charAt(i);
141
155
 
142
156
  if (c === ' ' || c === '\t' || c === '\n' || c === '\r') { i++; continue; }
143
157
 
@@ -188,6 +202,37 @@ function tokenize(sql) {
188
202
  continue;
189
203
  }
190
204
 
205
+ // 16 进制字面量:0x1F / 0XFF / X'1F' / x'FF'
206
+ // 标准 SQL:0x 前缀或 X'' 包裹,解析成十进制整数
207
+ if ((c === '0' && (sql[i + 1] === 'x' || sql[i + 1] === 'X')) ||
208
+ ((c === 'X' || c === 'x') && sql[i + 1] === "'")) {
209
+ if (c === '0') {
210
+ // 0x 前缀形式:0x1F, 0XFF
211
+ let j = i + 2;
212
+ while (j < n && /[0-9A-Fa-f]/.test(sql[j])) j++;
213
+ if (j === i + 2) throw new Error(`Invalid hexadecimal literal at position ${i}`);
214
+ const hexStr = sql.slice(i + 2, j);
215
+ const numVal = parseInt(hexStr, 16);
216
+ tokens.push(new SQLToken('number', numVal, i));
217
+ i = j;
218
+ continue;
219
+ } else {
220
+ // X'...' 形式:X'1F', x'FF'
221
+ let j = i + 2;
222
+ let hexStr = '';
223
+ while (j < n && sql[j] !== "'") {
224
+ if (!/[0-9A-Fa-f]/.test(sql[j])) throw new Error(`Invalid hexadecimal literal at position ${j}`);
225
+ hexStr += sql[j];
226
+ j++;
227
+ }
228
+ if (j >= n) throw new Error(`Unterminated hexadecimal literal at position ${i}`);
229
+ const numVal = parseInt(hexStr, 16);
230
+ tokens.push(new SQLToken('number', numVal, i));
231
+ i = j + 1;
232
+ continue;
233
+ }
234
+ }
235
+
191
236
  if (/[0-9]/.test(c) || (c === '.' && /[0-9]/.test(sql[i + 1] || ''))) {
192
237
  let j = i;
193
238
  let isFloat = false;
@@ -219,9 +264,16 @@ function tokenize(sql) {
219
264
  continue;
220
265
  }
221
266
 
222
- if (/[a-zA-Z_$]/.test(c)) {
223
- let j = i;
224
- while (j < n && /[a-zA-Z0-9_$]/.test(sql[j])) j++;
267
+ // Unicode 标识符:标准 SQL 允许非 ASCII 字母做列名/表名(中文、emoji 等)
268
+ // \p{L} = Unicode 字母,\p{So} = Unicode 其他符号(含 emoji),\p{M} = 组合标记
269
+ // 注意:不包含 \p{Sm}(数学符号如 + = < >)和 \p{P}(标点如 ( ) , ;)
270
+ if (/[\p{L}\p{So}\p{Sc}\p{M}_$]/u.test(c)) {
271
+ let j = i + cLen;
272
+ while (j < n) {
273
+ const { char: jc, length: jLen } = charAt(j);
274
+ if (!/[\p{L}\p{N}\p{So}\p{Sc}\p{M}_$]/u.test(jc)) break;
275
+ j += jLen;
276
+ }
225
277
  const word = sql.slice(i, j);
226
278
  const upper = word.toUpperCase();
227
279
  const tok = new SQLToken(KEYWORDS.has(upper) ? 'keyword' : 'ident', word, i);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "6.4.0-beta.3",
3
+ "version": "6.4.0-beta.5",
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",