jsql-neo 5.3.1 → 5.4.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
@@ -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.3.1** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
6
+ > **v5.4.0** — 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)
@@ -232,7 +232,7 @@ npm install && npm run build # option 3: from source
232
232
  Verify:
233
233
 
234
234
  ```bash
235
- node -e "console.log(require('jsql-neo/package.json').version)" # 5.3.1
235
+ node -e "console.log(require('jsql-neo/package.json').version)" # 5.4.0
236
236
  ```
237
237
 
238
238
  ### 30-second demo
@@ -1273,7 +1273,7 @@ jsql mod --engine wasm # switch engine (restart required)
1273
1273
 
1274
1274
  ```bash
1275
1275
  $ jsql version
1276
- jsql-neo v5.3.1
1276
+ jsql-neo v5.4.0
1277
1277
  engine: native (napi) | wasm | js
1278
1278
  node: v22.0.0 platform: linux x64
1279
1279
  ```
@@ -1294,7 +1294,7 @@ jsql tui --memory -q # memory mode, quiet
1294
1294
  jsql tui --prompt 'db> ' --no-color
1295
1295
  ```
1296
1296
 
1297
- The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=5.3.1`.
1297
+ The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=5.4.0`.
1298
1298
 
1299
1299
  ### Keyboard shortcuts
1300
1300
 
@@ -1710,7 +1710,7 @@ Conventions: feature branch + PR; tests for new SQL/commands; Conventional Commi
1710
1710
 
1711
1711
  ## License
1712
1712
 
1713
- MIT License — see the Chinese edition [License 许可证](#license-许可证) for the full text.
1713
+ Apache License 2.0 — see the Chinese edition [License 许可证](#license-许可证) for the full text.
1714
1714
 
1715
1715
  ---
1716
1716
 
@@ -3858,7 +3858,7 @@ Data dir: /root/.jsql-neo/data
3858
3858
 
3859
3859
  ```bash
3860
3860
  $ jsql version
3861
- jsql-neo v5.3.1
3861
+ jsql-neo v5.4.0
3862
3862
  engine: native (napi) | wasm | js
3863
3863
  node: v22.0.0
3864
3864
  platform: linux x64
@@ -4724,37 +4724,218 @@ npm run typecheck
4724
4724
 
4725
4725
  ## License 许可证
4726
4726
 
4727
- JSQL-NEO 采用 **MIT License**。
4728
-
4727
+ JSQL-NEO 采用 **Apache License 2.0**,完整许可文本见根目录 [LICENSE](LICENSE) 文件。
4728
+
4729
+ ```
4730
+ Apache License
4731
+ Version 2.0, January 2004
4732
+ http://www.apache.org/licenses/
4733
+
4734
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
4735
+
4736
+ 1. Definitions.
4737
+
4738
+ "License" shall mean the terms and conditions for use, reproduction,
4739
+ and distribution as defined by Sections 1 through 9 of this document.
4740
+
4741
+ "Licensor" shall mean the copyright owner or entity authorized by
4742
+ the copyright owner that is granting the License.
4743
+
4744
+ "Legal Entity" shall mean the union of the acting entity and all
4745
+ other entities that control, are controlled by, or are under common
4746
+ control with that entity. For the purposes of this definition,
4747
+ "control" means (i) the power, direct or indirect, to cause the
4748
+ direction or management of such entity, whether by contract or
4749
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
4750
+ outstanding shares, or (iii) beneficial ownership of such entity.
4751
+
4752
+ "You" (or "Your") shall mean an individual or Legal Entity
4753
+ exercising permissions granted by this License.
4754
+
4755
+ "Source" form shall mean the preferred form for making modifications,
4756
+ including but not limited to software source code, documentation
4757
+ source, and configuration files.
4758
+
4759
+ "Object" form shall mean any form resulting from mechanical
4760
+ transformation or translation of a Source form, including but
4761
+ not limited to compiled object code, generated documentation,
4762
+ and conversions to other media types.
4763
+
4764
+ "Work" shall mean the work of authorship, whether in Source or
4765
+ Object form, made available under the License, as indicated by a
4766
+ copyright notice that is included in or attached to the work
4767
+ (an example is provided in the Appendix below).
4768
+
4769
+ "Derivative Works" shall mean any work, whether in Source or Object
4770
+ form, that is based on (or derived from) the Work and for which the
4771
+ editorial revisions, annotations, elaborations, or other modifications
4772
+ represent, as a whole, an original work of authorship. For the purposes
4773
+ of this License, Derivative Works shall not include works that remain
4774
+ separable from, or merely link (or bind by name) to the interfaces of,
4775
+ the Work and Derivative Works thereof.
4776
+
4777
+ "Contribution" shall mean any work of authorship, including
4778
+ the original version of the Work and any modifications or additions
4779
+ to that Work or Derivative Works thereof, that is intentionally
4780
+ submitted to Licensor for inclusion in the Work by the copyright owner
4781
+ or by an individual or Legal Entity authorized to submit on behalf of
4782
+ the copyright owner. For the purposes of this definition, "submitted"
4783
+ means any form of electronic, verbal, or written communication sent
4784
+ to the Licensor or its representatives, including but not limited to
4785
+ communication on electronic mailing lists, source code control systems,
4786
+ and issue tracking systems that are managed by, or on behalf of, the
4787
+ Licensor for the purpose of discussing and improving the Work, but
4788
+ excluding communication that is conspicuously marked or otherwise
4789
+ designated in writing by the copyright owner as "Not a Contribution."
4790
+
4791
+ "Contributor" shall mean Licensor and any individual or Legal Entity
4792
+ on behalf of whom a Contribution has been received by Licensor and
4793
+ subsequently incorporated within the Work.
4794
+
4795
+ 2. Grant of Copyright License. Subject to the terms and conditions of
4796
+ this License, each Contributor hereby grants to You a perpetual,
4797
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
4798
+ copyright license to reproduce, prepare Derivative Works of,
4799
+ publicly display, publicly perform, sublicense, and distribute the
4800
+ Work and such Derivative Works in Source or Object form.
4801
+
4802
+ 3. Grant of Patent License. Subject to the terms and conditions of
4803
+ this License, each Contributor hereby grants to You a perpetual,
4804
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
4805
+ (except as stated in this section) patent license to make, have made,
4806
+ use, offer to sell, sell, import, and otherwise transfer the Work,
4807
+ where such license applies only to those patent claims licensable
4808
+ by such Contributor that are necessarily infringed by their
4809
+ Contribution(s) alone or by combination of their Contribution(s)
4810
+ with the Work to which such Contribution(s) was submitted. If You
4811
+ institute patent litigation against any entity (including a
4812
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
4813
+ or a Contribution incorporated within the Work constitutes direct
4814
+ or contributory patent infringement, then any patent licenses
4815
+ granted to You under this License for that Work shall terminate
4816
+ as of the date such litigation is filed.
4817
+
4818
+ 4. Redistribution. You may reproduce and distribute copies of the
4819
+ Work or Derivative Works thereof in any medium, with or without
4820
+ modifications, and in Source or Object form, provided that You
4821
+ meet the following conditions:
4822
+
4823
+ (a) You must give any other recipients of the Work or
4824
+ Derivative Works a copy of this License; and
4825
+
4826
+ (b) You must cause any modified files to carry prominent notices
4827
+ stating that You changed the files; and
4828
+
4829
+ (c) You must retain, in the Source form of any Derivative Works
4830
+ that You distribute, all copyright, patent, trademark, and
4831
+ attribution notices from the Source form of the Work,
4832
+ excluding those notices that do not pertain to any part of
4833
+ the Derivative Works; and
4834
+
4835
+ (d) If the Work includes a "NOTICE" text file as part of its
4836
+ distribution, then any Derivative Works that You distribute must
4837
+ include a readable copy of the attribution notices contained
4838
+ within such NOTICE file, excluding those notices that do not
4839
+ pertain to any part of the Derivative Works, in at least one
4840
+ of the following places: within a NOTICE text file distributed
4841
+ as part of the Derivative Works; within the Source form or
4842
+ documentation, if provided along with the Derivative Works; or,
4843
+ within a display generated by the Derivative Works, if and
4844
+ wherever such third-party notices normally appear. The contents
4845
+ of the NOTICE file are for informational purposes only and
4846
+ do not modify the License. You may add Your own attribution
4847
+ notices within Derivative Works that You distribute, alongside
4848
+ or as an addendum to the NOTICE text from the Work, provided
4849
+ that such additional attribution notices cannot be construed
4850
+ as modifying the License.
4851
+
4852
+ You may add Your own copyright statement to Your modifications and
4853
+ may provide additional or different license terms and conditions
4854
+ for use, reproduction, or distribution of Your modifications, or
4855
+ for any such Derivative Works as a whole, provided Your use,
4856
+ reproduction, and distribution of the Work otherwise complies with
4857
+ the conditions stated in this License.
4858
+
4859
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
4860
+ any Contribution intentionally submitted for inclusion in the Work
4861
+ by You to the Licensor shall be under the terms and conditions of
4862
+ this License, without any additional terms or conditions.
4863
+ Notwithstanding the above, nothing herein shall supersede or modify
4864
+ the terms of any separate license agreement you may have executed
4865
+ with Licensor regarding such Contributions.
4866
+
4867
+ 6. Trademarks. This License does not grant permission to use the trade
4868
+ names, trademarks, service marks, or product names of the Licensor,
4869
+ except as required for reasonable and customary use in describing the
4870
+ origin of the Work and reproducing the content of the NOTICE file.
4871
+
4872
+ 7. Disclaimer of Warranty. Unless required by applicable law or
4873
+ agreed to in writing, Licensor provides the Work (and each
4874
+ Contributor provides its Contributions) on an "AS IS" BASIS,
4875
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
4876
+ implied, including, without limitation, any warranties or conditions
4877
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
4878
+ PARTICULAR PURPOSE. You are solely responsible for determining the
4879
+ appropriateness of using or redistributing the Work and assume any
4880
+ risks associated with Your exercise of permissions under this License.
4881
+
4882
+ 8. Limitation of Liability. In no event and under no legal theory,
4883
+ whether in tort (including negligence), contract, or otherwise,
4884
+ unless required by applicable law (such as deliberate and grossly
4885
+ negligent acts) or agreed to in writing, shall any Contributor be
4886
+ liable to You for damages, including any direct, indirect, special,
4887
+ incidental, or consequential damages of any character arising as a
4888
+ result of this License or out of the use or inability to use the
4889
+ Work (including but not limited to damages for loss of goodwill,
4890
+ work stoppage, computer failure or malfunction, or any and all
4891
+ other commercial damages or losses), even if such Contributor
4892
+ has been advised of the possibility of such damages.
4893
+
4894
+ 9. Accepting Warranty or Additional Liability. While redistributing
4895
+ the Work or Derivative Works thereof, You may choose to offer,
4896
+ and charge a fee for, acceptance of support, warranty, indemnity,
4897
+ or other liability obligations and/or rights consistent with this
4898
+ License. However, in accepting such obligations, You may act only
4899
+ on Your own behalf and on Your sole responsibility, not on behalf
4900
+ of any other Contributor, and only if You agree to indemnify,
4901
+ defend, and hold each Contributor harmless for any liability
4902
+ incurred by, or claims asserted against, such Contributor by reason
4903
+ of your accepting any such warranty or additional liability.
4904
+
4905
+ END OF TERMS AND CONDITIONS
4906
+
4907
+ APPENDIX: How to apply the Apache License to your work.
4908
+
4909
+ To apply the Apache License to your work, attach the following
4910
+ boilerplate notice, with the fields enclosed by brackets "[]"
4911
+ replaced with your own identifying information. (Don't include
4912
+ the brackets!) The text should be enclosed in the appropriate
4913
+ comment syntax for the file format. We also recommend that a
4914
+ file or class name and description of purpose be included on the
4915
+ same "printed page" as the copyright notice for easier
4916
+ identification within third-party archives.
4917
+
4918
+ Copyright [yyyy] [name of copyright owner]
4919
+
4920
+ Licensed under the Apache License, Version 2.0 (the "License");
4921
+ you may not use this file except in compliance with the License.
4922
+ You may obtain a copy of the License at
4923
+
4924
+ http://www.apache.org/licenses/LICENSE-2.0
4925
+
4926
+ Unless required by applicable law or agreed to in writing, software
4927
+ distributed under the License is distributed on an "AS IS" BASIS,
4928
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4929
+ See the License for the specific language governing permissions and
4930
+ limitations under the License.
4729
4931
  ```
4730
- MIT License
4731
-
4732
- Copyright (c) 2026 vexify-org
4733
-
4734
- Permission is hereby granted, free of charge, to any person obtaining a copy
4735
- of this software and associated documentation files (the "Software"), to deal
4736
- in the Software without restriction, including without limitation the rights
4737
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4738
- copies of the Software, and to permit persons to whom the Software is
4739
- furnished to do so, subject to the following conditions:
4740
-
4741
- The above copyright notice and this permission notice shall be included in all
4742
- copies or substantial portions of the Software.
4743
-
4744
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4745
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
4746
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
4747
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
4748
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
4749
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
4750
- SOFTWARE.
4751
4932
  ```
4752
4933
 
4753
4934
  ---
4754
4935
 
4755
4936
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
4756
4937
 
4757
- *文档版本:v5.3.1 · 最后更新:2026-08-12*
4938
+ *文档版本:v5.4.0 · 最后更新:2026-08-12*
4758
4939
 
4759
4940
  ---
4760
4941
 
@@ -6524,7 +6705,7 @@ Usage: jsql version
6524
6705
 
6525
6706
  输出版本与环境信息:
6526
6707
 
6527
- jsql-neo v5.3.1
6708
+ jsql-neo v5.4.0
6528
6709
  engine: native (napi) | wasm | js
6529
6710
  node: v22.0.0
6530
6711
  platform: linux x64
@@ -6788,6 +6969,32 @@ new TUIShell({
6788
6969
 
6789
6970
  ## 附录 N:版本历史 CHANGELOG
6790
6971
 
6972
+ ### v5.4.0 (2026-08-30)
6973
+
6974
+ **新增**
6975
+ - WASM 事务支持:`jsql_begin_tx` / `jsql_commit_tx` / `jsql_rollback_tx` 绑定,
6976
+ WASM 客户端 `beginTransaction()` / `commit()` / `rollback()` 与原生一致
6977
+ - 主键感知 by-id CRUD 与整串简写 schema(`createTable(name, 'id integer primary key auto_increment, ...')`)
6978
+ - `test/wasm.test.js`(20 项)纳入 `test:all`
6979
+
6980
+ **变更**
6981
+ - 版本号升至 5.4.0(package.json / Cargo.toml / Mongo `buildInfo` / README 同步)
6982
+ - 移除构建产物与死代码:`nativesrc/*/target/`(1155 个文件)、`bin/jsql-neo-server`(ELF)、
6983
+ 根级 `database.js`/`btree.js` 副本、`lib/compress_pool.js`/`lib/compress_worker.js`
6984
+
6985
+ ### v5.2.0-beta.1 (2026-08-17)
6986
+
6987
+ **新增**
6988
+ - 主键感知的 by-id CRUD:`findById` / `findByIds` / `updateById(s)` / `removeById(s)`
6989
+ 在表存在主键时按主键值解析(字符串主键如 `findById('config', 'a')` 直接可用)
6990
+ - 非自增主键表 `insert` 返回主键值;`NativeJSQL` 新增
6991
+ `beginTransaction()` / `commit()` / `rollback()` 事务别名
6992
+ - 原生 / WASM 客户端新增 9 个回归测试
6993
+
6994
+ **变更**
6995
+ - 版本号重新基线 5.2.0-beta.1(package.json / Mongo `buildInfo` / README 同步)
6996
+ - 开源许可统一为 **Apache License 2.0**(README 同步,与 package.json 声明一致)
6997
+
6791
6998
  ### v5.3.1 (2026-08-12)
6792
6999
 
6793
7000
  **修复**
@@ -7154,7 +7361,7 @@ CI(GitHub Actions)矩阵:`node 20/22` × `linux/macos/windows` × `native/
7154
7361
 
7155
7362
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
7156
7363
 
7157
- *文档版本:v5.3.1 · 共 19 个附录 · 最后更新:2026-08-12*
7364
+ *文档版本:v5.4.0 · 共 19 个附录 · 最后更新:2026-08-12*
7158
7365
 
7159
7366
  ---
7160
7367
 
@@ -7761,4 +7968,4 @@ npm test
7761
7968
 
7762
7969
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
7763
7970
 
7764
- *文档版本:v5.3.1 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-08-12*
7971
+ *文档版本:v5.4.0 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-08-12*
@@ -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.3.1-jsql-neo', gitVersion: 'jsql-neo', versionArray: [5, 3, 1, 0] };
325
+ case 'buildInfo': return { ok: 1, version: '5.4.0-jsql-neo', gitVersion: 'jsql-neo', versionArray: [5, 4, 0, 0] };
326
326
  case 'getParameter': return { ok: 1 };
327
327
  case 'endSessions': return { ok: 1 };
328
328
  case 'listDatabases':
@@ -30,14 +30,52 @@ const NATIVE_TYPE_MAP = {
30
30
  int: 'integer',
31
31
  };
32
32
 
33
+ function parseShorthandSchema(str) {
34
+ const def = { type: str };
35
+ const strip = (re) => {
36
+ const next = def.type.replace(re, '').trim();
37
+ def.type = next || def.type;
38
+ };
39
+ if (/\bprimary\s+key\b/i.test(def.type)) { def.primaryKey = true; strip(/\bprimary\s+key\b/gi); }
40
+ if (/\bauto_?increment\b/i.test(def.type)) { def.autoIncrement = true; strip(/\bauto_?increment\b/gi); }
41
+ if (/\bnot\s+null\b/i.test(def.type)) { def.required = true; strip(/\bnot\s+null\b/gi); }
42
+ if (/\bunique\b/i.test(def.type)) { def.unique = true; strip(/\bunique\b/gi); }
43
+ if (/\bdefault\s+(\S+)/i.test(def.type)) {
44
+ const m = def.type.match(/\bdefault\s+(\S+)/i);
45
+ def.default = m[1].replace(/^['"]|['"]$/g, '');
46
+ strip(/\bdefault\s+\S+/gi);
47
+ }
48
+ return def;
49
+ }
50
+
51
+ function parseSchemaString(str) {
52
+ const out = {};
53
+ for (const seg of str.split(',')) {
54
+ const trimmed = seg.trim();
55
+ if (!trimmed) continue;
56
+ const parts = trimmed.split(/\s+/);
57
+ const name = parts.shift();
58
+ const def = parseShorthandSchema(parts.join(' ').toLowerCase().trim());
59
+ if (def.type.length === 0) def.type = 'string';
60
+ out[name] = def;
61
+ }
62
+ return out;
63
+ }
64
+
33
65
  function mapNativeSchema(schema) {
66
+ if (typeof schema === 'string') schema = parseSchemaString(schema);
34
67
  const mapped = {};
35
68
  for (const [field, def] of Object.entries(schema || {})) {
36
69
  if (typeof def === 'string') {
37
- mapped[field] = { type: NATIVE_TYPE_MAP[def.toLowerCase()] || def.toLowerCase() };
70
+ const parsed = parseShorthandSchema(def.toLowerCase().trim());
71
+ if (parsed.type.length === 0) parsed.type = 'string';
72
+ mapped[field] = { ...parsed, type: NATIVE_TYPE_MAP[parsed.type] || parsed.type };
38
73
  } else if (def && typeof def === 'object') {
39
74
  const t = (def.type || 'string').toLowerCase();
40
- mapped[field] = { ...def, type: NATIVE_TYPE_MAP[t] || t };
75
+ const out = { ...def, type: NATIVE_TYPE_MAP[t] || t };
76
+ if (out.primary_key !== undefined && out.primaryKey === undefined) { out.primaryKey = out.primary_key; delete out.primary_key; }
77
+ if (out.auto_increment !== undefined && out.autoIncrement === undefined) { out.autoIncrement = out.auto_increment; delete out.auto_increment; }
78
+ mapped[field] = out;
41
79
  } else {
42
80
  mapped[field] = { type: 'string' };
43
81
  }
@@ -46,25 +84,46 @@ function mapNativeSchema(schema) {
46
84
  }
47
85
 
48
86
  function restoreRow(row, schema) {
49
- if (!schema || typeof row !== 'object' || row === null) return row;
50
- const out = {};
51
- for (const [field, def] of Object.entries(row)) {
52
- if (field === 'fields' && typeof row[field] === 'object' && row[field] !== null) {
53
- out[field] = restoreRow(row[field], schema);
54
- continue;
87
+ if (typeof row !== 'object' || row === null) return row;
88
+ let src = row;
89
+ if (row.fields && typeof row.fields === 'object' && row.fields !== null) {
90
+ src = { ...row.fields };
91
+ if (row.id !== undefined) {
92
+ const pkCols = [];
93
+ if (schema) {
94
+ for (const [k, def] of Object.entries(schema)) {
95
+ if (def && typeof def === 'object' && def.autoIncrement) continue;
96
+ const isPk = typeof def === 'string'
97
+ ? /\bprimary\s+key\b/i.test(def)
98
+ : !!(def && (def.primaryKey || def.primary_key));
99
+ if (isPk) pkCols.push(k);
100
+ }
101
+ } else if (src.id === undefined) {
102
+ pkCols.push('id');
103
+ }
104
+ for (const c of pkCols) {
105
+ const v = src[c];
106
+ if (v === undefined || v === null || v === 0 || v === '' || v === false) src[c] = row.id;
107
+ }
55
108
  }
109
+ if (row.created_at !== undefined) src.created_at = row.created_at;
110
+ if (row.updated_at !== undefined) src.updated_at = row.updated_at;
111
+ }
112
+ if (!schema) return src;
113
+ const out = {};
114
+ for (const [field, value] of Object.entries(src)) {
56
115
  const colDef = schema[field];
57
116
  let t = null;
58
117
  if (typeof colDef === 'string') t = colDef.toLowerCase();
59
118
  else if (colDef && typeof colDef === 'object' && colDef.type) t = String(colDef.type).toLowerCase();
60
- if (['json', 'array', 'object'].includes(t) && typeof row[field] === 'string') {
61
- try { out[field] = JSON.parse(row[field]); } catch (e) { out[field] = row[field]; }
62
- } else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof row[field] === 'string' && /^-?\d+$/.test(row[field])) {
63
- out[field] = Number(row[field]);
64
- } else if ((t === 'float' || t === 'double' || t === 'number') && typeof row[field] === 'string' && !Number.isNaN(Number(row[field]))) {
65
- out[field] = Number(row[field]);
119
+ if (['json', 'array', 'object'].includes(t) && typeof value === 'string') {
120
+ try { out[field] = JSON.parse(value); } catch (e) { out[field] = value; }
121
+ } else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof value === 'string' && /^-?\d+$/.test(value)) {
122
+ out[field] = Number(value);
123
+ } else if ((t === 'float' || t === 'double' || t === 'number') && typeof value === 'string' && !Number.isNaN(Number(value))) {
124
+ out[field] = Number(value);
66
125
  } else {
67
- out[field] = row[field];
126
+ out[field] = value;
68
127
  }
69
128
  }
70
129
  return out;
@@ -154,6 +213,7 @@ class JSQL {
154
213
  this._opFlushInterval = opts.opFlushInterval || 50;
155
214
  this._tableNames = new Set();
156
215
  this._schemas = {};
216
+ this._txId = undefined;
157
217
  this._autoPlugins = opts.modules !== false;
158
218
 
159
219
  // 持久化模式: 'memory' | 'hybrid' | 'disk'
@@ -313,6 +373,62 @@ class JSQL {
313
373
  }
314
374
  }
315
375
 
376
+ _pkOf(table) {
377
+ const schema = this._schemas[table];
378
+ if (!schema) return null;
379
+ for (const [k, def] of Object.entries(schema)) {
380
+ const isPk = (typeof def === 'string' && /\bprimary\s+key\b/i.test(def))
381
+ || !!(def && typeof def === 'object' && (def.primaryKey || def.primary_key));
382
+ if (isPk) return k;
383
+ }
384
+ return null;
385
+ }
386
+
387
+ _pkAutoIncrement(table) {
388
+ const schema = this._schemas[table];
389
+ const pk = this._pkOf(table);
390
+ if (!pk || !schema) return false;
391
+ const def = schema[pk];
392
+ if (typeof def === 'string') return /\bauto_?increment\b/i.test(def);
393
+ return !!(def && def.autoIncrement);
394
+ }
395
+
396
+ _coercePkId(table, id) {
397
+ const schema = this._schemas[table];
398
+ const pk = this._pkOf(table);
399
+ if (!schema || !pk) return id;
400
+ const def = schema[pk];
401
+ let t = null;
402
+ if (typeof def === 'string') t = def.toLowerCase();
403
+ else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
404
+ if (['integer', 'int', 'bigint'].includes(t)) return Number(id);
405
+ return id;
406
+ }
407
+
408
+ _findRawById(table, id) {
409
+ const pk = this._pkOf(table);
410
+ if (pk) {
411
+ const filter = {};
412
+ filter[pk] = this._coercePkId(table, id);
413
+ const r = safeParse(native.jsqlFind(table, JSON.stringify(filter), 1, 0));
414
+ if (r && r.error) return r;
415
+ return Array.isArray(r) && r.length > 0 ? r[0] : null;
416
+ }
417
+ return safeParse(native.jsqlFindById(table, Number(id)));
418
+ }
419
+
420
+ _seqIdByPk(table, id) {
421
+ const pk = this._pkOf(table);
422
+ if (pk) {
423
+ const filter = {};
424
+ filter[pk] = this._coercePkId(table, id);
425
+ const r = safeParse(native.jsqlFind(table, JSON.stringify(filter), 1, 0));
426
+ if (Array.isArray(r) && r.length > 0) return r[0].id;
427
+ return null;
428
+ }
429
+ return Number(id);
430
+ }
431
+
316
432
  async _insertBatch(table, rows) {
317
433
  const schema = this._schemas[table];
318
434
  if (schema) {
@@ -328,6 +444,15 @@ class JSQL {
328
444
  const bin = encodeBatch(rows);
329
445
  const r = safeParse(native.jsqlInsertBuf(table, bin));
330
446
  if (r && r.error) throw new Error(r.error);
447
+ if (r && Array.isArray(r) && !this._pkAutoIncrement(table)) {
448
+ const pk = this._pkOf(table);
449
+ if (pk) {
450
+ return r.map((seq, i) => {
451
+ const row = rows[i];
452
+ return (row && row[pk] !== undefined && row[pk] !== null) ? row[pk] : seq;
453
+ });
454
+ }
455
+ }
331
456
  return r;
332
457
  }
333
458
 
@@ -419,7 +544,7 @@ class JSQL {
419
544
  const r = safeParse(native.jsqlCreateTable(name, JSON.stringify(mapNativeSchema(schema))));
420
545
  if (r && r.ok === false) throw new Error(r.error || 'create table failed');
421
546
  this._tableNames.add(name);
422
- this._schemas[name] = schema;
547
+ this._schemas[name] = typeof schema === 'string' ? mapNativeSchema(schema) : schema;
423
548
  this._emit('createTable', { name, schema });
424
549
  this._runHooks('afterCreateTable', [name, schema]);
425
550
  return r;
@@ -440,7 +565,7 @@ class JSQL {
440
565
  findById(table, id) {
441
566
  this._flushOpsNow();
442
567
  if (!this._runHooks('beforeFind', [table, { id }])) return null;
443
- const raw = safeParse(native.jsqlFindById(table, Number(id)));
568
+ const raw = this._findRawById(table, id);
444
569
  if (raw && raw.error) throw new Error(raw.error);
445
570
  this._runHooks('afterFind', [table, { id }, raw]);
446
571
  const schema = this._schemas[table];
@@ -451,8 +576,12 @@ class JSQL {
451
576
  findByIds(table, ids) {
452
577
  this._flushOpsNow();
453
578
  if (!this._runHooks('beforeFind', [table, { ids }])) return null;
454
- const resultStr = native.jsqlFindByIds(table, JSON.stringify(ids));
455
- const r = safeParse(resultStr);
579
+ let r;
580
+ if (this._pkOf(table)) {
581
+ r = ids.map(id => this._findRawById(table, id)).filter(x => x !== null && !(x && x.error));
582
+ } else {
583
+ r = safeParse(native.jsqlFindByIds(table, JSON.stringify(ids)));
584
+ }
456
585
  if (r && r.error) throw new Error(r.error);
457
586
  this._runHooks('afterFind', [table, { ids }, r]);
458
587
  const schema = this._schemas[table];
@@ -487,7 +616,11 @@ class JSQL {
487
616
  }
488
617
 
489
618
  updateByIds(table, entries) {
490
- const pairs = entries.map(([id, data]) => [Number(id), data]);
619
+ const pairs = [];
620
+ for (const [id, data] of entries) {
621
+ const seq = this._seqIdByPk(table, id);
622
+ if (seq !== null) pairs.push([seq, data]);
623
+ }
491
624
  if (!this._runHooks('beforeUpdate', [table, pairs])) return;
492
625
  if (!this._opBuffer.update[table]) this._opBuffer.update[table] = [];
493
626
  this._opBuffer.update[table].push(...pairs);
@@ -497,21 +630,28 @@ class JSQL {
497
630
  removeByIds(table, ids) {
498
631
  if (!this._runHooks('beforeDelete', [table, ids])) return;
499
632
  if (!this._opBuffer.remove[table]) this._opBuffer.remove[table] = new Set();
500
- for (const id of ids) this._opBuffer.remove[table].add(Number(id));
633
+ for (const id of ids) {
634
+ const seq = this._seqIdByPk(table, id);
635
+ if (seq !== null) this._opBuffer.remove[table].add(seq);
636
+ }
501
637
  this._scheduleOpFlush();
502
638
  }
503
639
 
504
640
  updateById(table, id, data) {
505
641
  if (!this._runHooks('beforeUpdate', [table, id, data])) return;
642
+ const seq = this._seqIdByPk(table, id);
643
+ if (seq === null) return;
506
644
  if (!this._opBuffer.update[table]) this._opBuffer.update[table] = [];
507
- this._opBuffer.update[table].push([Number(id), data]);
645
+ this._opBuffer.update[table].push([seq, data]);
508
646
  this._scheduleOpFlush();
509
647
  }
510
648
 
511
649
  removeById(table, id) {
512
650
  if (!this._runHooks('beforeDelete', [table, id])) return;
651
+ const seq = this._seqIdByPk(table, id);
652
+ if (seq === null) return;
513
653
  if (!this._opBuffer.remove[table]) this._opBuffer.remove[table] = new Set();
514
- this._opBuffer.remove[table].add(Number(id));
654
+ this._opBuffer.remove[table].add(seq);
515
655
  this._scheduleOpFlush();
516
656
  }
517
657
 
@@ -543,9 +683,28 @@ class JSQL {
543
683
  this._flushOpsNow();
544
684
  const r = safeParse(native.jsqlBeginTx());
545
685
  if (r && r.ok === false) throw new Error(r.error || 'begin transaction failed');
686
+ this._txId = r.txId;
546
687
  return r.txId;
547
688
  }
548
689
 
690
+ async beginTransaction() {
691
+ return this.beginTx();
692
+ }
693
+
694
+ async commit() {
695
+ if (this._txId === undefined) return true;
696
+ const r = await this.commitTx(this._txId);
697
+ this._txId = undefined;
698
+ return r;
699
+ }
700
+
701
+ async rollback() {
702
+ if (this._txId === undefined) return true;
703
+ const r = await this.rollbackTx(this._txId);
704
+ this._txId = undefined;
705
+ return r;
706
+ }
707
+
549
708
  async commitTx(txId) {
550
709
  const r = safeParse(native.jsqlCommitTx(String(txId)));
551
710
  if (r && r.ok === false) throw new Error(r.error || 'commit transaction failed');