jsql-neo 5.5.0 → 6.0.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.
Files changed (81) hide show
  1. package/LICENSE +202 -202
  2. package/README.md +8155 -8097
  3. package/bin/jsql +320 -320
  4. package/bin/jsql-server +90 -90
  5. package/bin/jsql-server-control.js +177 -177
  6. package/index.d.ts +412 -385
  7. package/index.js +128 -124
  8. package/lib/ast.js +214 -214
  9. package/lib/btree.js +478 -413
  10. package/lib/cache.js +328 -328
  11. package/lib/client.js +125 -125
  12. package/lib/database.js +1741 -1744
  13. package/lib/date-types.js +182 -182
  14. package/lib/errors.js +89 -89
  15. package/lib/jsql_format.js +497 -497
  16. package/lib/migrate.js +254 -254
  17. package/lib/mod.js +292 -292
  18. package/lib/mongo_server.js +607 -607
  19. package/lib/multiserver.js +140 -140
  20. package/lib/mysql_compat.js +413 -413
  21. package/lib/mysql_server.js +1237 -1237
  22. package/lib/native_client.js +722 -732
  23. package/lib/nedb_compat.js +506 -506
  24. package/lib/pg_server.js +864 -864
  25. package/lib/plugin.js +259 -35
  26. package/lib/plugins/audit.js +37 -0
  27. package/lib/plugins/index.js +72 -0
  28. package/lib/plugins/logger.js +35 -0
  29. package/lib/plugins/metrics.js +38 -0
  30. package/lib/plugins/timestamps.js +57 -0
  31. package/lib/plugins/validation.js +128 -0
  32. package/lib/prepared.js +37 -0
  33. package/lib/query.js +834 -815
  34. package/lib/redis_server.js +571 -571
  35. package/lib/sql.js +4053 -3460
  36. package/lib/sqlite_compat.js +323 -323
  37. package/lib/sqlite_worker.js +264 -264
  38. package/lib/table.js +1351 -1304
  39. package/lib/tui.js +502 -502
  40. package/lib/wasm.js +1 -1
  41. package/lib/wasm_client.js +715 -718
  42. package/lib/web_ui.js +257 -257
  43. package/native/jsql-neo-native.node +0 -0
  44. package/nativesrc/jsql-neo-core/Cargo.lock +462 -455
  45. package/nativesrc/jsql-neo-core/Cargo.toml +26 -24
  46. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +492 -455
  47. package/nativesrc/jsql-neo-core/src/engine/memory.rs +191 -168
  48. package/nativesrc/jsql-neo-core/src/engine/mod.rs +74 -62
  49. package/nativesrc/jsql-neo-core/src/engine/table.rs +996 -700
  50. package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
  51. package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
  52. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
  53. package/nativesrc/jsql-neo-core/src/storage/wal.rs +298 -85
  54. package/nativesrc/jsql-neo-core/src/types.rs +93 -93
  55. package/nativesrc/jsql-neo-native/Cargo.lock +613 -606
  56. package/nativesrc/jsql-neo-native/Cargo.toml +24 -16
  57. package/nativesrc/jsql-neo-native/build.rs +3 -3
  58. package/nativesrc/jsql-neo-native/package.json +7 -7
  59. package/nativesrc/jsql-neo-native/src/lib.rs +281 -269
  60. package/nativesrc/jsql-neo-wasm/Cargo.lock +472 -465
  61. package/nativesrc/jsql-neo-wasm/Cargo.toml +22 -15
  62. package/nativesrc/jsql-neo-wasm/src/lib.rs +463 -447
  63. package/package.json +87 -87
  64. package/postinstall.js +158 -158
  65. package/test/btree.test.js +144 -128
  66. package/test/coverage.js +187 -187
  67. package/test/join.test.js +110 -110
  68. package/test/native.test.js +158 -158
  69. package/test/query-opt.test.js +109 -0
  70. package/test/regress-5.1.0.js +271 -271
  71. package/test/smoke.js +58 -58
  72. package/test/sql-parser.test.js +450 -273
  73. package/test/wasm.test.js +110 -110
  74. package/wasm/browser.d.ts +89 -89
  75. package/wasm/browser.mjs +591 -569
  76. package/wasm/browser_bg.mjs +618 -597
  77. package/wasm/jsql_neo_wasm.d.ts +44 -38
  78. package/wasm/jsql_neo_wasm.js +641 -520
  79. package/wasm/jsql_neo_wasm_bg.wasm +0 -0
  80. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
  81. package/wasm/package.json +11 -11
@@ -1,718 +1,715 @@
1
- const path = require('path');
2
- const { executeSQL } = require('./sql');
3
- const wasmBindings = require(path.join(__dirname, '..', 'wasm', 'jsql_neo_wasm.js'));
4
-
5
- function safeJsonParse(str) {
6
- try { return JSON.parse(str); } catch { return str; }
7
- }
8
-
9
- const NATIVE_TYPE_MAP = {
10
- text: 'string',
11
- varchar: 'string',
12
- double: 'float',
13
- number: 'float',
14
- numeric: 'float',
15
- decimal: 'float',
16
- date: 'string',
17
- timestamp: 'string',
18
- datetime: 'string',
19
- json: 'string',
20
- array: 'string',
21
- object: 'string',
22
- bool: 'boolean',
23
- bigint: 'string',
24
- int: 'integer',
25
- };
26
-
27
- function parseShorthandSchema(str) {
28
- const def = { type: str };
29
- const strip = (re) => {
30
- const next = def.type.replace(re, '').trim();
31
- def.type = next || def.type;
32
- };
33
- if (/\bprimary\s+key\b/i.test(def.type)) { def.primaryKey = true; strip(/\bprimary\s+key\b/gi); }
34
- if (/\bauto_?increment\b/i.test(def.type)) { def.autoIncrement = true; strip(/\bauto_?increment\b/gi); }
35
- if (/\bnot\s+null\b/i.test(def.type)) { def.required = true; strip(/\bnot\s+null\b/gi); }
36
- if (/\bunique\b/i.test(def.type)) { def.unique = true; strip(/\bunique\b/gi); }
37
- if (/\bdefault\s+(\S+)/i.test(def.type)) {
38
- const m = def.type.match(/\bdefault\s+(\S+)/i);
39
- def.default = m[1].replace(/^['"]|['"]$/g, '');
40
- strip(/\bdefault\s+\S+/gi);
41
- }
42
- return def;
43
- }
44
-
45
- function parseSchemaString(str) {
46
- const out = {};
47
- for (const seg of str.split(',')) {
48
- const trimmed = seg.trim();
49
- if (!trimmed) continue;
50
- const parts = trimmed.split(/\s+/);
51
- const name = parts.shift();
52
- const def = parseShorthandSchema(parts.join(' ').toLowerCase().trim());
53
- if (def.type.length === 0) def.type = 'string';
54
- out[name] = def;
55
- }
56
- return out;
57
- }
58
-
59
- function mapNativeSchema(schema) {
60
- if (typeof schema === 'string') schema = parseSchemaString(schema);
61
- const mapped = {};
62
- for (const [field, def] of Object.entries(schema || {})) {
63
- if (typeof def === 'string') {
64
- const parsed = parseShorthandSchema(def.toLowerCase().trim());
65
- if (parsed.type.length === 0) parsed.type = 'string';
66
- mapped[field] = { ...parsed, type: NATIVE_TYPE_MAP[parsed.type] || parsed.type };
67
- } else if (def && typeof def === 'object') {
68
- const t = (def.type || 'string').toLowerCase();
69
- const out = { ...def, type: NATIVE_TYPE_MAP[t] || t };
70
- if (out.primary_key !== undefined && out.primaryKey === undefined) { out.primaryKey = out.primary_key; delete out.primary_key; }
71
- if (out.auto_increment !== undefined && out.autoIncrement === undefined) { out.autoIncrement = out.auto_increment; delete out.auto_increment; }
72
- mapped[field] = out;
73
- } else {
74
- mapped[field] = { type: 'string' };
75
- }
76
- }
77
- return mapped;
78
- }
79
-
80
- function restoreRow(row, schema) {
81
- if (typeof row !== 'object' || row === null) return row;
82
- let src = row;
83
- if (row.fields && typeof row.fields === 'object' && row.fields !== null) {
84
- src = { ...row.fields };
85
- if (row.id !== undefined) {
86
- const pkCols = [];
87
- if (schema) {
88
- for (const [k, def] of Object.entries(schema)) {
89
- if (def && typeof def === 'object' && def.autoIncrement) continue;
90
- const isPk = typeof def === 'string'
91
- ? /\bprimary\s+key\b/i.test(def)
92
- : !!(def && (def.primaryKey || def.primary_key));
93
- if (isPk) pkCols.push(k);
94
- }
95
- } else if (src.id === undefined) {
96
- pkCols.push('id');
97
- }
98
- for (const c of pkCols) {
99
- const v = src[c];
100
- if (v === undefined || v === null || v === 0 || v === '' || v === false) src[c] = row.id;
101
- }
102
- }
103
- if (row.created_at !== undefined) src.created_at = row.created_at;
104
- if (row.updated_at !== undefined) src.updated_at = row.updated_at;
105
- }
106
- if (!schema) return src;
107
- const out = {};
108
- for (const [field, value] of Object.entries(src)) {
109
- const colDef = schema[field];
110
- let t = null;
111
- if (typeof colDef === 'string') t = colDef.toLowerCase();
112
- else if (colDef && typeof colDef === 'object' && colDef.type) t = String(colDef.type).toLowerCase();
113
- if (['json', 'array', 'object'].includes(t) && typeof value === 'string') {
114
- try { out[field] = JSON.parse(value); } catch (e) { out[field] = value; }
115
- } else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof value === 'string' && /^-?\d+$/.test(value)) {
116
- out[field] = Number(value);
117
- } else if ((t === 'float' || t === 'double' || t === 'number') && typeof value === 'string' && !Number.isNaN(Number(value))) {
118
- out[field] = Number(value);
119
- } else {
120
- out[field] = value;
121
- }
122
- }
123
- return out;
124
- }
125
-
126
- const INT64_TAG = 1;
127
- const FLOAT_TAG = 2;
128
- const STR_TAG = 3;
129
- const BOOL_TAG = 4;
130
- const INT32_TAG = 5;
131
-
132
- function encodeBatch(rows) {
133
- if (rows.length === 0) return new Uint8Array(0);
134
- const fieldNames = Object.keys(rows[0]);
135
- const nFields = fieldNames.length;
136
-
137
- // Estimate buffer: header ~100 + rows * 120 bytes/row
138
- const est = 100 + rows.length * 120;
139
- const buf = Buffer.allocUnsafe(est);
140
- let off = 0;
141
-
142
- off = buf.writeUInt8(nFields, off);
143
- for (let fi = 0; fi < nFields; fi++) {
144
- const s = fieldNames[fi];
145
- const nlen = Buffer.byteLength(s, 'utf8');
146
- off = buf.writeUInt8(nlen, off);
147
- off += buf.write(s, off, nlen, 'utf8');
148
- }
149
- off = buf.writeUInt32LE(rows.length, off);
150
-
151
- for (let ri = 0; ri < rows.length; ri++) {
152
- const row = rows[ri];
153
- let fi = 0;
154
- if (nFields >= 1) {
155
- const v = row[fieldNames[0]];
156
- if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
157
- else if (typeof v === 'number') {
158
- if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
159
- else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
160
- } else if (typeof v === 'string') {
161
- off = buf.writeUInt8(STR_TAG, off);
162
- const sl = Buffer.byteLength(v, 'utf8');
163
- off = buf.writeUInt32LE(sl, off);
164
- off += buf.write(v, off, sl, 'utf8');
165
- } else if (typeof v === 'boolean') {
166
- off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
167
- } else {
168
- const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
169
- off = buf.writeUInt8(STR_TAG, off);
170
- const sl = Buffer.byteLength(s, 'utf8');
171
- off = buf.writeUInt32LE(sl, off);
172
- off += buf.write(s, off, sl, 'utf8');
173
- }
174
- }
175
- if (nFields >= 2) {
176
- const v = row[fieldNames[1]];
177
- if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
178
- else if (typeof v === 'number') {
179
- if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
180
- else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
181
- } else if (typeof v === 'string') {
182
- off = buf.writeUInt8(STR_TAG, off);
183
- const sl = Buffer.byteLength(v, 'utf8');
184
- off = buf.writeUInt32LE(sl, off);
185
- off += buf.write(v, off, sl, 'utf8');
186
- } else if (typeof v === 'boolean') {
187
- off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
188
- } else {
189
- const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
190
- off = buf.writeUInt8(STR_TAG, off);
191
- const sl = Buffer.byteLength(s, 'utf8');
192
- off = buf.writeUInt32LE(sl, off);
193
- off += buf.write(s, off, sl, 'utf8');
194
- }
195
- }
196
- if (nFields >= 3) {
197
- const v = row[fieldNames[2]];
198
- if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
199
- else if (typeof v === 'number') {
200
- if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
201
- else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
202
- } else if (typeof v === 'string') {
203
- off = buf.writeUInt8(STR_TAG, off);
204
- const sl = Buffer.byteLength(v, 'utf8');
205
- off = buf.writeUInt32LE(sl, off);
206
- off += buf.write(v, off, sl, 'utf8');
207
- } else if (typeof v === 'boolean') {
208
- off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
209
- } else {
210
- const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
211
- off = buf.writeUInt8(STR_TAG, off);
212
- const sl = Buffer.byteLength(s, 'utf8');
213
- off = buf.writeUInt32LE(sl, off);
214
- off += buf.write(s, off, sl, 'utf8');
215
- }
216
- }
217
- if (nFields >= 4) {
218
- const v = row[fieldNames[3]];
219
- if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
220
- else if (typeof v === 'number') {
221
- if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
222
- else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
223
- } else if (typeof v === 'string') {
224
- off = buf.writeUInt8(STR_TAG, off);
225
- const sl = Buffer.byteLength(v, 'utf8');
226
- off = buf.writeUInt32LE(sl, off);
227
- off += buf.write(v, off, sl, 'utf8');
228
- } else if (typeof v === 'boolean') {
229
- off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
230
- } else {
231
- const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
232
- off = buf.writeUInt8(STR_TAG, off);
233
- const sl = Buffer.byteLength(s, 'utf8');
234
- off = buf.writeUInt32LE(sl, off);
235
- off += buf.write(s, off, sl, 'utf8');
236
- }
237
- }
238
- if (nFields >= 5) {
239
- const v = row[fieldNames[4]];
240
- if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
241
- else if (typeof v === 'number') {
242
- if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
243
- else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
244
- } else if (typeof v === 'string') {
245
- off = buf.writeUInt8(STR_TAG, off);
246
- const sl = Buffer.byteLength(v, 'utf8');
247
- off = buf.writeUInt32LE(sl, off);
248
- off += buf.write(v, off, sl, 'utf8');
249
- } else if (typeof v === 'boolean') {
250
- off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
251
- } else {
252
- const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
253
- off = buf.writeUInt8(STR_TAG, off);
254
- const sl = Buffer.byteLength(s, 'utf8');
255
- off = buf.writeUInt32LE(sl, off);
256
- off += buf.write(s, off, sl, 'utf8');
257
- }
258
- }
259
- // fallback for >5 fields
260
- for (let fi = 5; fi < nFields; fi++) {
261
- const v = row[fieldNames[fi]];
262
- if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
263
- else if (typeof v === 'number') {
264
- if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
265
- else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
266
- } else if (typeof v === 'string') {
267
- off = buf.writeUInt8(STR_TAG, off);
268
- const sl = Buffer.byteLength(v, 'utf8');
269
- off = buf.writeUInt32LE(sl, off);
270
- off += buf.write(v, off, sl, 'utf8');
271
- } else if (typeof v === 'boolean') {
272
- off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
273
- } else {
274
- const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
275
- off = buf.writeUInt8(STR_TAG, off);
276
- const sl = Buffer.byteLength(s, 'utf8');
277
- off = buf.writeUInt32LE(sl, off);
278
- off += buf.write(s, off, sl, 'utf8');
279
- }
280
- }
281
- }
282
-
283
- return buf.subarray(0, off);
284
- }
285
-
286
- class JSQL {
287
- constructor(opts = {}) {
288
- this._flushThreshold = opts.flushThreshold || 5000;
289
- this._buffer = {};
290
- this._bufferSize = 0;
291
- this._tableNames = new Set();
292
- this._schemas = {};
293
- this._txId = undefined;
294
- this._autoPlugins = opts.modules !== false;
295
-
296
- this._plugins = [];
297
- this._hooks = {
298
- beforeInsert: [], afterInsert: [],
299
- beforeUpdate: [], afterUpdate: [],
300
- beforeDelete: [], afterDelete: [],
301
- beforeFind: [], afterFind: [],
302
- beforeCreateTable: [], afterCreateTable: [],
303
- beforeDropTable: [], afterDropTable: [],
304
- beforeFlush: [], afterFlush: [],
305
- beforeCount: [], afterCount: [],
306
- onStart: [], onStop: []
307
- };
308
- this._eventListeners = [];
309
- if (this._autoPlugins) {
310
- const { ModuleManager } = require('./mod');
311
- new ModuleManager().applyTo(this);
312
- }
313
- }
314
-
315
- use(plugin) {
316
- if (typeof plugin === 'function') {
317
- plugin = { install: plugin };
318
- }
319
- if (plugin.hooks) {
320
- for (const [event, fn] of Object.entries(plugin.hooks)) {
321
- if (this._hooks[event]) {
322
- this._hooks[event].push(fn);
323
- }
324
- }
325
- }
326
- if (typeof plugin.install === 'function') {
327
- plugin.install(this, this._buildCtx(plugin));
328
- }
329
- if (typeof plugin.onEvent === 'function') {
330
- this._eventListeners.push(plugin.onEvent);
331
- }
332
- this._plugins.push(plugin);
333
- return this;
334
- }
335
-
336
- _buildCtx(plugin) {
337
- const self = this;
338
- return {
339
- name: plugin.name || 'anonymous',
340
- engine: this,
341
- plugin,
342
- on(hook, fn) { return self.on(hook, fn); },
343
- onEvent(fn) { return self.onEvent(fn); },
344
- emit(eventName, data) { self._emit(eventName, data); },
345
- tables() { return Array.from(self._tableNames); },
346
- hasTable(name) { return self._tableNames.has(name); },
347
- getTableSchema(name) { return self._schemas[name] || null; }
348
- };
349
- }
350
-
351
- on(event, fn) {
352
- if (this._hooks[event]) {
353
- this._hooks[event].push(fn);
354
- }
355
- return this;
356
- }
357
-
358
- onEvent(fn) {
359
- this._eventListeners.push(fn);
360
- return this;
361
- }
362
-
363
- _emit(eventName, data) {
364
- for (const fn of this._eventListeners) {
365
- try { fn(eventName, data); } catch (e) { /* ignore */ }
366
- }
367
- }
368
-
369
- _runHooks(hookName, args) {
370
- const hooks = this._hooks[hookName];
371
- if (!hooks || hooks.length === 0) return true;
372
- for (const fn of hooks) {
373
- const r = fn(...args);
374
- if (r === false) return false;
375
- if (r !== undefined && args.length > 0) {
376
- args[0] = r;
377
- }
378
- }
379
- return true;
380
- }
381
-
382
- async start() {
383
- wasmBindings.jsql_reset();
384
- this._runHooks('onStart', []);
385
- this._emit('start', {});
386
- }
387
-
388
- _pkOf(table) {
389
- const schema = this._schemas[table];
390
- if (!schema) return null;
391
- for (const [k, def] of Object.entries(schema)) {
392
- const isPk = (typeof def === 'string' && /\bprimary\s+key\b/i.test(def))
393
- || !!(def && typeof def === 'object' && (def.primaryKey || def.primary_key));
394
- if (isPk) return k;
395
- }
396
- return null;
397
- }
398
-
399
- _pkAutoIncrement(table) {
400
- const schema = this._schemas[table];
401
- const pk = this._pkOf(table);
402
- if (!pk || !schema) return false;
403
- const def = schema[pk];
404
- if (typeof def === 'string') return /\bauto_?increment\b/i.test(def);
405
- return !!(def && def.autoIncrement);
406
- }
407
-
408
- _coercePkId(table, id) {
409
- const schema = this._schemas[table];
410
- const pk = this._pkOf(table);
411
- if (!schema || !pk) return id;
412
- const def = schema[pk];
413
- let t = null;
414
- if (typeof def === 'string') t = def.toLowerCase();
415
- else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
416
- if (['integer', 'int', 'bigint'].includes(t)) return Number(id);
417
- return id;
418
- }
419
-
420
- _findRawById(table, id) {
421
- const pk = this._pkOf(table);
422
- if (pk) {
423
- const filter = {};
424
- filter[pk] = this._coercePkId(table, id);
425
- const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
426
- if (r && r.error) return r;
427
- return Array.isArray(r) && r.length > 0 ? r[0] : null;
428
- }
429
- return safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
430
- }
431
-
432
- _seqIdByPk(table, id) {
433
- const pk = this._pkOf(table);
434
- if (pk) {
435
- const filter = {};
436
- filter[pk] = this._coercePkId(table, id);
437
- const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
438
- if (Array.isArray(r) && r.length > 0) return r[0].id;
439
- return null;
440
- }
441
- return Number(id);
442
- }
443
-
444
- async _insertBatch(table, rows) {
445
- const schema = this._schemas[table];
446
- if (schema) {
447
- rows = rows.map(row => {
448
- const out = {};
449
- for (const [k, v] of Object.entries(row)) {
450
- const def = schema[k];
451
- let t = null;
452
- if (typeof def === 'string') t = def.toLowerCase();
453
- else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
454
- if (v !== null && v !== undefined && ['json', 'array', 'object'].includes(t) && typeof v !== 'string') {
455
- out[k] = JSON.stringify(v);
456
- } else if (v instanceof Date) {
457
- out[k] = v.toISOString();
458
- } else {
459
- out[k] = v;
460
- }
461
- }
462
- return out;
463
- });
464
- }
465
- const r = safeJsonParse(wasmBindings.jsql_insert_json(table, JSON.stringify(rows)));
466
- if (r && r.error) throw new Error(r.error);
467
- if (r && Array.isArray(r) && !this._pkAutoIncrement(table)) {
468
- const pk = this._pkOf(table);
469
- if (pk) {
470
- return r.map((seq, i) => {
471
- const row = rows[i];
472
- return (row && row[pk] !== undefined && row[pk] !== null) ? row[pk] : seq;
473
- });
474
- }
475
- }
476
- return r;
477
- }
478
-
479
- async _flush() {
480
- if (!this._runHooks('beforeFlush', [])) return null;
481
- const flushed = {};
482
- for (const [table, rows] of Object.entries(this._buffer)) {
483
- if (rows.length === 0) continue;
484
- const r = await this._insertBatch(table, rows);
485
- flushed[table] = r;
486
- }
487
- this._buffer = {};
488
- this._bufferSize = 0;
489
- this._runHooks('afterFlush', []);
490
- return flushed;
491
- }
492
-
493
- async flush() {
494
- await this._flush();
495
- }
496
-
497
- async insert(table, data) {
498
- const arr = Array.isArray(data) ? data : [data];
499
- if (!this._runHooks('beforeInsert', [table, arr])) return [];
500
- if (arr.length > 1) {
501
- await this._flush();
502
- let result;
503
- for (let i = 0; i < arr.length; i += this._flushThreshold) {
504
- const chunk = arr.slice(i, i + this._flushThreshold);
505
- const r = await this._insertBatch(table, chunk);
506
- if (r && r.error) throw new Error(r.error);
507
- if (!result) result = r;
508
- }
509
- this._emit('insert', { table, count: arr.length, ids: result });
510
- this._runHooks('afterInsert', [table, arr, result]);
511
- return result;
512
- }
513
- if (!this._buffer[table]) this._buffer[table] = [];
514
- this._buffer[table].push(arr[0]);
515
- this._bufferSize++;
516
- const flushed = await this._flush();
517
- return flushed && flushed[table] ? flushed[table] : null;
518
- }
519
-
520
- async insertMany(table, data) {
521
- return this.insert(table, data);
522
- }
523
-
524
- async createTable(name, schema) {
525
- await this._flush();
526
- if (!this._runHooks('beforeCreateTable', [name, schema])) return null;
527
- const r = safeJsonParse(wasmBindings.jsql_create_table(name, JSON.stringify(mapNativeSchema(schema))));
528
- if (r && r.ok === false) throw new Error(r.error || 'create table failed');
529
- this._tableNames.add(name);
530
- this._schemas[name] = typeof schema === 'string' ? mapNativeSchema(schema) : schema;
531
- this._emit('createTable', { name, schema });
532
- this._runHooks('afterCreateTable', [name, schema]);
533
- return r;
534
- }
535
-
536
- async dropTable(name) {
537
- await this._flush();
538
- if (!this._runHooks('beforeDropTable', [name])) return null;
539
- const r = safeJsonParse(wasmBindings.jsql_drop_table(name));
540
- if (r && r.ok === false) throw new Error(r.error || 'drop table failed');
541
- this._tableNames.delete(name);
542
- delete this._schemas[name];
543
- this._emit('dropTable', { name });
544
- this._runHooks('afterDropTable', [name]);
545
- return r;
546
- }
547
-
548
- async findById(table, id) {
549
- await this._flush();
550
- if (!this._runHooks('beforeFind', [table, { id }])) return null;
551
- const r = this._findRawById(table, id);
552
- if (r && r.error) throw new Error(r.error);
553
- this._runHooks('afterFind', [table, { id }, r]);
554
- const schema = this._schemas[table];
555
- if (schema && r && typeof r === 'object') return restoreRow(r, schema);
556
- return r;
557
- }
558
-
559
- async find(table, filter, opts = {}) {
560
- await this._flush();
561
- if (!this._runHooks('beforeFind', [table, { filter, opts }])) return [];
562
- const filterStr = filter ? JSON.stringify(filter) : '';
563
- const { limit = 100, offset = 0 } = opts;
564
- const r = safeJsonParse(wasmBindings.jsql_find(table, filterStr, limit, offset));
565
- if (r && r.error) throw new Error(r.error);
566
- this._runHooks('afterFind', [table, { filter, opts }, r]);
567
- const schema = this._schemas[table];
568
- if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
569
- return r;
570
- }
571
-
572
- async count(table) {
573
- await this._flush();
574
- this._runHooks('beforeCount', [table]);
575
- const r = parseInt(wasmBindings.jsql_count(table), 10);
576
- const n = isNaN(r) ? 0 : r;
577
- this._runHooks('afterCount', [table, n]);
578
- return n;
579
- }
580
-
581
- async updateByIds(table, entries) {
582
- const pairs = [];
583
- for (const [id, data] of entries) {
584
- const seq = this._seqIdByPk(table, id);
585
- if (seq !== null) pairs.push([seq, data]);
586
- }
587
- if (!this._runHooks('beforeUpdate', [table, pairs])) return;
588
- await this._flush();
589
- const r = safeJsonParse(wasmBindings.jsql_update_by_ids(table, JSON.stringify(pairs)));
590
- if (r && r.error) throw new Error(r.error);
591
- this._emit('update', { table, entries: pairs, result: r });
592
- this._runHooks('afterUpdate', [table, pairs, r]);
593
- return r;
594
- }
595
-
596
- async removeByIds(table, ids) {
597
- if (!this._runHooks('beforeDelete', [table, ids])) return;
598
- await this._flush();
599
- const seqs = ids.map(id => this._seqIdByPk(table, id)).filter(s => s !== null);
600
- const r = safeJsonParse(wasmBindings.jsql_remove_by_ids(table, JSON.stringify(seqs)));
601
- if (r && r.error) throw new Error(r.error);
602
- this._emit('delete', { table, ids: seqs, result: r });
603
- this._runHooks('afterDelete', [table, ids, r]);
604
- return r;
605
- }
606
-
607
- async findByIds(table, ids) {
608
- await this._flush();
609
- if (!this._runHooks('beforeFind', [table, { ids }])) return null;
610
- let r;
611
- if (this._pkOf(table)) {
612
- r = ids.map(id => this._findRawById(table, id)).filter(x => x !== null && !(x && x.error));
613
- } else {
614
- r = safeJsonParse(wasmBindings.jsql_find_by_ids(table, JSON.stringify(ids)));
615
- }
616
- if (r && r.error) throw new Error(r.error);
617
- this._runHooks('afterFind', [table, { ids }, r]);
618
- const schema = this._schemas[table];
619
- if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
620
- return r;
621
- }
622
-
623
- findByIdsRaw(table, ids) {
624
- return this.findByIds(table, ids);
625
- }
626
-
627
- async updateById(table, id, data) {
628
- if (!this._runHooks('beforeUpdate', [table, id, data])) return;
629
- await this._flush();
630
- const seq = this._seqIdByPk(table, id);
631
- if (seq === null) return null;
632
- const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(seq), JSON.stringify(data)));
633
- if (r && r.ok === false) throw new Error(r.error || 'update failed');
634
- this._emit('update', { table, id, data });
635
- this._runHooks('afterUpdate', [table, id, data, r]);
636
- return r;
637
- }
638
-
639
- async removeById(table, id) {
640
- if (!this._runHooks('beforeDelete', [table, id])) return;
641
- await this._flush();
642
- const seq = this._seqIdByPk(table, id);
643
- if (seq === null) return null;
644
- const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(seq)));
645
- if (r && r.ok === false) throw new Error(r.error || 'remove failed');
646
- this._emit('delete', { table, id });
647
- this._runHooks('afterDelete', [table, id, r]);
648
- return r;
649
- }
650
-
651
- async beginTx() {
652
- await this._flush();
653
- const r = safeJsonParse(wasmBindings.jsql_begin_tx());
654
- if (r && r.ok === false) throw new Error(r.error || 'begin transaction failed');
655
- this._txId = r.txId;
656
- return r.txId;
657
- }
658
-
659
- async commitTx(txId) {
660
- const r = safeJsonParse(wasmBindings.jsql_commit_tx(String(txId)));
661
- if (r && r.ok === false) throw new Error(r.error || 'commit transaction failed');
662
- return true;
663
- }
664
-
665
- async rollbackTx(txId) {
666
- const r = safeJsonParse(wasmBindings.jsql_rollback_tx(String(txId)));
667
- if (r && r.ok === false) throw new Error(r.error || 'rollback transaction failed');
668
- return true;
669
- }
670
-
671
- async beginTransaction() {
672
- return this.beginTx();
673
- }
674
-
675
- async commit() {
676
- if (this._txId === undefined) return true;
677
- const r = await this.commitTx(this._txId);
678
- this._txId = undefined;
679
- return r;
680
- }
681
-
682
- async rollback() {
683
- if (this._txId === undefined) return true;
684
- const r = await this.rollbackTx(this._txId);
685
- this._txId = undefined;
686
- return r;
687
- }
688
-
689
- async hasTable(name) {
690
- return this._tableNames.has(name);
691
- }
692
-
693
- async getTables() {
694
- return Array.from(this._tableNames);
695
- }
696
-
697
- async getTableSchema(name) {
698
- return this._schemas[name] || null;
699
- }
700
-
701
- /**
702
- * 直接执行原生 SQL 语句。本实例自身即充当 engine,
703
- * 支持 SELECT / INSERT / UPDATE / DELETE / CREATE TABLE 等常用语句。
704
- * @example
705
- * const rows = await db.query("SELECT * FROM users WHERE age > ?", [21]);
706
- */
707
- async query(sql, paramsOrOpts, opts) {
708
- return executeSQL(this, sql, paramsOrOpts, opts);
709
- }
710
-
711
- async stop() {
712
- await this._flush();
713
- this._runHooks('onStop', []);
714
- this._emit('stop', {});
715
- }
716
- }
717
-
718
- module.exports = { JSQL };
1
+ const path = require('path');
2
+ const { executeSQL } = require('./sql');
3
+ const {
4
+ createHookRegistry, applyPlugin, buildPluginContext, registerHook,
5
+ runHooks, getPluginApis
6
+ } = require('./plugin');
7
+ const wasmBindings = require(path.join(__dirname, '..', 'wasm', 'jsql_neo_wasm.js'));
8
+
9
+ function safeJsonParse(str) {
10
+ try { return JSON.parse(str); } catch { return str; }
11
+ }
12
+
13
+ const NATIVE_TYPE_MAP = {
14
+ text: 'string',
15
+ varchar: 'string',
16
+ double: 'float',
17
+ number: 'float',
18
+ numeric: 'float',
19
+ decimal: 'float',
20
+ date: 'string',
21
+ timestamp: 'string',
22
+ datetime: 'string',
23
+ json: 'string',
24
+ array: 'string',
25
+ object: 'string',
26
+ bool: 'boolean',
27
+ bigint: 'string',
28
+ int: 'integer',
29
+ };
30
+
31
+ function parseShorthandSchema(str) {
32
+ const def = { type: str };
33
+ const strip = (re) => {
34
+ const next = def.type.replace(re, '').trim();
35
+ def.type = next || def.type;
36
+ };
37
+ if (/\bprimary\s+key\b/i.test(def.type)) { def.primaryKey = true; strip(/\bprimary\s+key\b/gi); }
38
+ if (/\bauto_?increment\b/i.test(def.type)) { def.autoIncrement = true; strip(/\bauto_?increment\b/gi); }
39
+ if (/\bnot\s+null\b/i.test(def.type)) { def.required = true; strip(/\bnot\s+null\b/gi); }
40
+ if (/\bunique\b/i.test(def.type)) { def.unique = true; strip(/\bunique\b/gi); }
41
+ if (/\bdefault\s+(\S+)/i.test(def.type)) {
42
+ const m = def.type.match(/\bdefault\s+(\S+)/i);
43
+ def.default = m[1].replace(/^['"]|['"]$/g, '');
44
+ strip(/\bdefault\s+\S+/gi);
45
+ }
46
+ return def;
47
+ }
48
+
49
+ function parseSchemaString(str) {
50
+ const out = {};
51
+ for (const seg of str.split(',')) {
52
+ const trimmed = seg.trim();
53
+ if (!trimmed) continue;
54
+ const parts = trimmed.split(/\s+/);
55
+ const name = parts.shift();
56
+ const def = parseShorthandSchema(parts.join(' ').toLowerCase().trim());
57
+ if (def.type.length === 0) def.type = 'string';
58
+ out[name] = def;
59
+ }
60
+ return out;
61
+ }
62
+
63
+ function mapNativeSchema(schema) {
64
+ if (typeof schema === 'string') schema = parseSchemaString(schema);
65
+ const mapped = {};
66
+ for (const [field, def] of Object.entries(schema || {})) {
67
+ if (typeof def === 'string') {
68
+ const parsed = parseShorthandSchema(def.toLowerCase().trim());
69
+ if (parsed.type.length === 0) parsed.type = 'string';
70
+ mapped[field] = { ...parsed, type: NATIVE_TYPE_MAP[parsed.type] || parsed.type };
71
+ } else if (def && typeof def === 'object') {
72
+ const t = (def.type || 'string').toLowerCase();
73
+ const out = { ...def, type: NATIVE_TYPE_MAP[t] || t };
74
+ if (out.primary_key !== undefined && out.primaryKey === undefined) { out.primaryKey = out.primary_key; delete out.primary_key; }
75
+ if (out.auto_increment !== undefined && out.autoIncrement === undefined) { out.autoIncrement = out.auto_increment; delete out.auto_increment; }
76
+ mapped[field] = out;
77
+ } else {
78
+ mapped[field] = { type: 'string' };
79
+ }
80
+ }
81
+ return mapped;
82
+ }
83
+
84
+ function restoreRow(row, schema) {
85
+ if (typeof row !== 'object' || row === null) return row;
86
+ let src = row;
87
+ if (row.fields && typeof row.fields === 'object' && row.fields !== null) {
88
+ src = { ...row.fields };
89
+ if (row.id !== undefined) {
90
+ const pkCols = [];
91
+ if (schema) {
92
+ for (const [k, def] of Object.entries(schema)) {
93
+ if (def && typeof def === 'object' && def.autoIncrement) continue;
94
+ const isPk = typeof def === 'string'
95
+ ? /\bprimary\s+key\b/i.test(def)
96
+ : !!(def && (def.primaryKey || def.primary_key));
97
+ if (isPk) pkCols.push(k);
98
+ }
99
+ } else if (src.id === undefined) {
100
+ pkCols.push('id');
101
+ }
102
+ for (const c of pkCols) {
103
+ const v = src[c];
104
+ if (v === undefined || v === null || v === 0 || v === '' || v === false) src[c] = row.id;
105
+ }
106
+ }
107
+ if (row.created_at !== undefined) src.created_at = row.created_at;
108
+ if (row.updated_at !== undefined) src.updated_at = row.updated_at;
109
+ }
110
+ if (!schema) return src;
111
+ const out = {};
112
+ for (const [field, value] of Object.entries(src)) {
113
+ const colDef = schema[field];
114
+ let t = null;
115
+ if (typeof colDef === 'string') t = colDef.toLowerCase();
116
+ else if (colDef && typeof colDef === 'object' && colDef.type) t = String(colDef.type).toLowerCase();
117
+ if (['json', 'array', 'object'].includes(t) && typeof value === 'string') {
118
+ try { out[field] = JSON.parse(value); } catch (e) { out[field] = value; }
119
+ } else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof value === 'string' && /^-?\d+$/.test(value)) {
120
+ out[field] = Number(value);
121
+ } else if ((t === 'float' || t === 'double' || t === 'number') && typeof value === 'string' && !Number.isNaN(Number(value))) {
122
+ out[field] = Number(value);
123
+ } else {
124
+ out[field] = value;
125
+ }
126
+ }
127
+ return out;
128
+ }
129
+
130
+ const INT64_TAG = 1;
131
+ const FLOAT_TAG = 2;
132
+ const STR_TAG = 3;
133
+ const BOOL_TAG = 4;
134
+ const INT32_TAG = 5;
135
+
136
+ function encodeBatch(rows) {
137
+ if (rows.length === 0) return new Uint8Array(0);
138
+ const fieldNames = Object.keys(rows[0]);
139
+ const nFields = fieldNames.length;
140
+
141
+ // Estimate buffer: header ~100 + rows * 120 bytes/row
142
+ const est = 100 + rows.length * 120;
143
+ const buf = Buffer.allocUnsafe(est);
144
+ let off = 0;
145
+
146
+ off = buf.writeUInt8(nFields, off);
147
+ for (let fi = 0; fi < nFields; fi++) {
148
+ const s = fieldNames[fi];
149
+ const nlen = Buffer.byteLength(s, 'utf8');
150
+ off = buf.writeUInt8(nlen, off);
151
+ off += buf.write(s, off, nlen, 'utf8');
152
+ }
153
+ off = buf.writeUInt32LE(rows.length, off);
154
+
155
+ for (let ri = 0; ri < rows.length; ri++) {
156
+ const row = rows[ri];
157
+ let fi = 0;
158
+ if (nFields >= 1) {
159
+ const v = row[fieldNames[0]];
160
+ if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
161
+ else if (typeof v === 'number') {
162
+ if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
163
+ else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
164
+ } else if (typeof v === 'string') {
165
+ off = buf.writeUInt8(STR_TAG, off);
166
+ const sl = Buffer.byteLength(v, 'utf8');
167
+ off = buf.writeUInt32LE(sl, off);
168
+ off += buf.write(v, off, sl, 'utf8');
169
+ } else if (typeof v === 'boolean') {
170
+ off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
171
+ } else {
172
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
173
+ off = buf.writeUInt8(STR_TAG, off);
174
+ const sl = Buffer.byteLength(s, 'utf8');
175
+ off = buf.writeUInt32LE(sl, off);
176
+ off += buf.write(s, off, sl, 'utf8');
177
+ }
178
+ }
179
+ if (nFields >= 2) {
180
+ const v = row[fieldNames[1]];
181
+ if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
182
+ else if (typeof v === 'number') {
183
+ if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
184
+ else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
185
+ } else if (typeof v === 'string') {
186
+ off = buf.writeUInt8(STR_TAG, off);
187
+ const sl = Buffer.byteLength(v, 'utf8');
188
+ off = buf.writeUInt32LE(sl, off);
189
+ off += buf.write(v, off, sl, 'utf8');
190
+ } else if (typeof v === 'boolean') {
191
+ off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
192
+ } else {
193
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
194
+ off = buf.writeUInt8(STR_TAG, off);
195
+ const sl = Buffer.byteLength(s, 'utf8');
196
+ off = buf.writeUInt32LE(sl, off);
197
+ off += buf.write(s, off, sl, 'utf8');
198
+ }
199
+ }
200
+ if (nFields >= 3) {
201
+ const v = row[fieldNames[2]];
202
+ if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
203
+ else if (typeof v === 'number') {
204
+ if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
205
+ else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
206
+ } else if (typeof v === 'string') {
207
+ off = buf.writeUInt8(STR_TAG, off);
208
+ const sl = Buffer.byteLength(v, 'utf8');
209
+ off = buf.writeUInt32LE(sl, off);
210
+ off += buf.write(v, off, sl, 'utf8');
211
+ } else if (typeof v === 'boolean') {
212
+ off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
213
+ } else {
214
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
215
+ off = buf.writeUInt8(STR_TAG, off);
216
+ const sl = Buffer.byteLength(s, 'utf8');
217
+ off = buf.writeUInt32LE(sl, off);
218
+ off += buf.write(s, off, sl, 'utf8');
219
+ }
220
+ }
221
+ if (nFields >= 4) {
222
+ const v = row[fieldNames[3]];
223
+ if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
224
+ else if (typeof v === 'number') {
225
+ if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
226
+ else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
227
+ } else if (typeof v === 'string') {
228
+ off = buf.writeUInt8(STR_TAG, off);
229
+ const sl = Buffer.byteLength(v, 'utf8');
230
+ off = buf.writeUInt32LE(sl, off);
231
+ off += buf.write(v, off, sl, 'utf8');
232
+ } else if (typeof v === 'boolean') {
233
+ off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
234
+ } else {
235
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
236
+ off = buf.writeUInt8(STR_TAG, off);
237
+ const sl = Buffer.byteLength(s, 'utf8');
238
+ off = buf.writeUInt32LE(sl, off);
239
+ off += buf.write(s, off, sl, 'utf8');
240
+ }
241
+ }
242
+ if (nFields >= 5) {
243
+ const v = row[fieldNames[4]];
244
+ if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
245
+ else if (typeof v === 'number') {
246
+ if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
247
+ else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
248
+ } else if (typeof v === 'string') {
249
+ off = buf.writeUInt8(STR_TAG, off);
250
+ const sl = Buffer.byteLength(v, 'utf8');
251
+ off = buf.writeUInt32LE(sl, off);
252
+ off += buf.write(v, off, sl, 'utf8');
253
+ } else if (typeof v === 'boolean') {
254
+ off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
255
+ } else {
256
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
257
+ off = buf.writeUInt8(STR_TAG, off);
258
+ const sl = Buffer.byteLength(s, 'utf8');
259
+ off = buf.writeUInt32LE(sl, off);
260
+ off += buf.write(s, off, sl, 'utf8');
261
+ }
262
+ }
263
+ // fallback for >5 fields
264
+ for (let fi = 5; fi < nFields; fi++) {
265
+ const v = row[fieldNames[fi]];
266
+ if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
267
+ else if (typeof v === 'number') {
268
+ if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
269
+ else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
270
+ } else if (typeof v === 'string') {
271
+ off = buf.writeUInt8(STR_TAG, off);
272
+ const sl = Buffer.byteLength(v, 'utf8');
273
+ off = buf.writeUInt32LE(sl, off);
274
+ off += buf.write(v, off, sl, 'utf8');
275
+ } else if (typeof v === 'boolean') {
276
+ off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
277
+ } else {
278
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
279
+ off = buf.writeUInt8(STR_TAG, off);
280
+ const sl = Buffer.byteLength(s, 'utf8');
281
+ off = buf.writeUInt32LE(sl, off);
282
+ off += buf.write(s, off, sl, 'utf8');
283
+ }
284
+ }
285
+ }
286
+
287
+ return buf.subarray(0, off);
288
+ }
289
+
290
+ class JSQL {
291
+ constructor(opts = {}) {
292
+ this._flushThreshold = opts.flushThreshold || 5000;
293
+ this._buffer = {};
294
+ this._bufferSize = 0;
295
+ this._tableNames = new Set();
296
+ this._schemas = {};
297
+ this._txId = undefined;
298
+ this._autoPlugins = opts.modules !== false;
299
+
300
+ this._plugins = [];
301
+ this._hooks = createHookRegistry();
302
+ this._eventListeners = [];
303
+ if (this._autoPlugins) {
304
+ const { ModuleManager } = require('./mod');
305
+ new ModuleManager().applyTo(this);
306
+ }
307
+ }
308
+
309
+ use(plugin, opts) {
310
+ return applyPlugin(this, plugin, opts);
311
+ }
312
+
313
+ _buildCtx(plugin) {
314
+ return buildPluginContext(this, plugin);
315
+ }
316
+
317
+ on(event, fn) {
318
+ return registerHook(this, event, fn);
319
+ }
320
+
321
+ onEvent(fn) {
322
+ this._eventListeners.push(fn);
323
+ return this;
324
+ }
325
+
326
+ /** 广播事件(插件上下文 / 外部均可调用) */
327
+ emit(eventName, data) {
328
+ this._emit(eventName, data);
329
+ return this;
330
+ }
331
+
332
+ /** 主动触发一个钩子(含插件自定义钩子) */
333
+ runHook(hookName, ...args) {
334
+ return this._runHooks(hookName, args);
335
+ }
336
+
337
+ /** 取插件对外暴露的 API(由 plugin.api 声明) */
338
+ plugin(name) {
339
+ return getPluginApis(this).get(name) || null;
340
+ }
341
+
342
+ _emit(eventName, data) {
343
+ for (const fn of this._eventListeners) {
344
+ try { fn(eventName, data); } catch (e) { /* ignore */ }
345
+ }
346
+ }
347
+
348
+ _runHooks(hookName, args) {
349
+ return runHooks(this, hookName, args);
350
+ }
351
+
352
+ async start() {
353
+ wasmBindings.jsql_reset();
354
+ this._runHooks('onStart', []);
355
+ this._emit('start', {});
356
+ }
357
+
358
+ _pkOf(table) {
359
+ const schema = this._schemas[table];
360
+ if (!schema) return null;
361
+ for (const [k, def] of Object.entries(schema)) {
362
+ const isPk = (typeof def === 'string' && /\bprimary\s+key\b/i.test(def))
363
+ || !!(def && typeof def === 'object' && (def.primaryKey || def.primary_key));
364
+ if (isPk) return k;
365
+ }
366
+ return null;
367
+ }
368
+
369
+ _pkAutoIncrement(table) {
370
+ const schema = this._schemas[table];
371
+ const pk = this._pkOf(table);
372
+ if (!pk || !schema) return false;
373
+ const def = schema[pk];
374
+ if (typeof def === 'string') return /\bauto_?increment\b/i.test(def);
375
+ return !!(def && def.autoIncrement);
376
+ }
377
+
378
+ _coercePkId(table, id) {
379
+ const schema = this._schemas[table];
380
+ const pk = this._pkOf(table);
381
+ if (!schema || !pk) return id;
382
+ const def = schema[pk];
383
+ let t = null;
384
+ if (typeof def === 'string') t = def.toLowerCase();
385
+ else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
386
+ if (['integer', 'int', 'bigint'].includes(t)) return Number(id);
387
+ return id;
388
+ }
389
+
390
+ _findRawById(table, id) {
391
+ const pk = this._pkOf(table);
392
+ if (pk) {
393
+ const filter = {};
394
+ filter[pk] = this._coercePkId(table, id);
395
+ const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
396
+ if (r && r.error) return r;
397
+ return Array.isArray(r) && r.length > 0 ? r[0] : null;
398
+ }
399
+ return safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
400
+ }
401
+
402
+ _seqIdByPk(table, id) {
403
+ const pk = this._pkOf(table);
404
+ if (pk) {
405
+ const filter = {};
406
+ filter[pk] = this._coercePkId(table, id);
407
+ const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
408
+ if (Array.isArray(r) && r.length > 0) return r[0].id;
409
+ return null;
410
+ }
411
+ return Number(id);
412
+ }
413
+
414
+ async _insertBatch(table, rows) {
415
+ const schema = this._schemas[table];
416
+ if (schema) {
417
+ rows = rows.map(row => {
418
+ const out = {};
419
+ for (const [k, v] of Object.entries(row)) {
420
+ const def = schema[k];
421
+ let t = null;
422
+ if (typeof def === 'string') t = def.toLowerCase();
423
+ else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
424
+ if (v !== null && v !== undefined && ['json', 'array', 'object'].includes(t) && typeof v !== 'string') {
425
+ out[k] = JSON.stringify(v);
426
+ } else if (v instanceof Date) {
427
+ out[k] = v.toISOString();
428
+ } else {
429
+ out[k] = v;
430
+ }
431
+ }
432
+ return out;
433
+ });
434
+ }
435
+ const r = safeJsonParse(wasmBindings.jsql_insert_json(table, JSON.stringify(rows)));
436
+ if (r && r.error) throw new Error(r.error);
437
+ if (r && Array.isArray(r) && !this._pkAutoIncrement(table)) {
438
+ const pk = this._pkOf(table);
439
+ if (pk) {
440
+ return r.map((seq, i) => {
441
+ const row = rows[i];
442
+ return (row && row[pk] !== undefined && row[pk] !== null) ? row[pk] : seq;
443
+ });
444
+ }
445
+ }
446
+ return r;
447
+ }
448
+
449
+ async _flush() {
450
+ if (!this._runHooks('beforeFlush', [])) return null;
451
+ const flushed = {};
452
+ for (const [table, rows] of Object.entries(this._buffer)) {
453
+ if (rows.length === 0) continue;
454
+ const r = await this._insertBatch(table, rows);
455
+ flushed[table] = r;
456
+ }
457
+ this._buffer = {};
458
+ this._bufferSize = 0;
459
+ this._runHooks('afterFlush', []);
460
+ return flushed;
461
+ }
462
+
463
+ async flush() {
464
+ await this._flush();
465
+ }
466
+
467
+ async insert(table, data) {
468
+ const arr = Array.isArray(data) ? data : [data];
469
+ if (!this._runHooks('beforeInsert', [table, arr])) return [];
470
+ if (arr.length > 1) {
471
+ await this._flush();
472
+ let result;
473
+ for (let i = 0; i < arr.length; i += this._flushThreshold) {
474
+ const chunk = arr.slice(i, i + this._flushThreshold);
475
+ const r = await this._insertBatch(table, chunk);
476
+ if (r && r.error) throw new Error(r.error);
477
+ if (!result) result = r;
478
+ }
479
+ this._emit('insert', { table, count: arr.length, ids: result });
480
+ this._runHooks('afterInsert', [table, arr, result]);
481
+ return result;
482
+ }
483
+ if (!this._buffer[table]) this._buffer[table] = [];
484
+ this._buffer[table].push(arr[0]);
485
+ this._bufferSize++;
486
+ const flushed = await this._flush();
487
+ const result = flushed && flushed[table] ? flushed[table] : null;
488
+ this._emit('insert', { table, count: arr.length, ids: result });
489
+ this._runHooks('afterInsert', [table, arr, result]);
490
+ return result;
491
+ }
492
+
493
+ async insertMany(table, data) {
494
+ return this.insert(table, data);
495
+ }
496
+
497
+ async createTable(name, schema) {
498
+ await this._flush();
499
+ if (typeof schema === 'string') schema = parseSchemaString(schema);
500
+ if (!this._runHooks('beforeCreateTable', [name, schema])) return null;
501
+ const r = safeJsonParse(wasmBindings.jsql_create_table(name, JSON.stringify(mapNativeSchema(schema))));
502
+ if (r && r.ok === false) throw new Error(r.error || 'create table failed');
503
+ this._tableNames.add(name);
504
+ this._schemas[name] = typeof schema === 'string' ? mapNativeSchema(schema) : schema;
505
+ this._emit('createTable', { name, schema });
506
+ this._runHooks('afterCreateTable', [name, schema]);
507
+ return r;
508
+ }
509
+
510
+ async addColumn(table, name, def) {
511
+ await this._flush();
512
+ const mapped = mapNativeSchema({ [name]: def })[name];
513
+ const r = safeJsonParse(wasmBindings.jsql_add_column(table, name, JSON.stringify(mapped)));
514
+ if (r && r.ok === false) throw new Error(r.error || 'add column failed');
515
+ if (!this._schemas[table]) this._schemas[table] = {};
516
+ this._schemas[table][name] = mapped;
517
+ this._emit('addColumn', { table, name, def: mapped });
518
+ return r;
519
+ }
520
+
521
+ async dropTable(name) {
522
+ await this._flush();
523
+ if (!this._runHooks('beforeDropTable', [name])) return null;
524
+ const r = safeJsonParse(wasmBindings.jsql_drop_table(name));
525
+ if (r && r.ok === false) throw new Error(r.error || 'drop table failed');
526
+ this._tableNames.delete(name);
527
+ delete this._schemas[name];
528
+ this._emit('dropTable', { name });
529
+ this._runHooks('afterDropTable', [name]);
530
+ return r;
531
+ }
532
+
533
+ async findById(table, id) {
534
+ await this._flush();
535
+ if (!this._runHooks('beforeFind', [table, { id }])) return null;
536
+ const r = this._findRawById(table, id);
537
+ if (r && r.error) throw new Error(r.error);
538
+ this._runHooks('afterFind', [table, { id }, r]);
539
+ const schema = this._schemas[table];
540
+ if (schema && r && typeof r === 'object') return restoreRow(r, schema);
541
+ return r;
542
+ }
543
+
544
+ async find(table, filter, opts = {}) {
545
+ await this._flush();
546
+ if (!this._runHooks('beforeFind', [table, { filter, opts }])) return [];
547
+ const filterStr = filter ? JSON.stringify(filter) : '';
548
+ const { limit = 100, offset = 0 } = opts;
549
+ const r = safeJsonParse(wasmBindings.jsql_find(table, filterStr, limit, offset));
550
+ if (r && r.error) throw new Error(r.error);
551
+ this._runHooks('afterFind', [table, { filter, opts }, r]);
552
+ const schema = this._schemas[table];
553
+ if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
554
+ return r;
555
+ }
556
+
557
+ async count(table) {
558
+ await this._flush();
559
+ this._runHooks('beforeCount', [table]);
560
+ const r = parseInt(wasmBindings.jsql_count(table), 10);
561
+ const n = isNaN(r) ? 0 : r;
562
+ this._runHooks('afterCount', [table, n]);
563
+ return n;
564
+ }
565
+
566
+ async updateByIds(table, entries) {
567
+ const pairs = [];
568
+ for (const [id, data] of entries) {
569
+ const seq = this._seqIdByPk(table, id);
570
+ if (seq !== null) pairs.push([seq, data]);
571
+ }
572
+ if (!this._runHooks('beforeUpdate', [table, pairs])) return;
573
+ await this._flush();
574
+ const r = safeJsonParse(wasmBindings.jsql_update_by_ids(table, JSON.stringify(pairs)));
575
+ if (r && r.error) throw new Error(r.error);
576
+ this._emit('update', { table, entries: pairs, result: r });
577
+ this._runHooks('afterUpdate', [table, pairs, r]);
578
+ return r;
579
+ }
580
+
581
+ async removeByIds(table, ids) {
582
+ if (!this._runHooks('beforeDelete', [table, ids])) return;
583
+ await this._flush();
584
+ const seqs = ids.map(id => this._seqIdByPk(table, id)).filter(s => s !== null);
585
+ const r = safeJsonParse(wasmBindings.jsql_remove_by_ids(table, JSON.stringify(seqs)));
586
+ if (r && r.error) throw new Error(r.error);
587
+ this._emit('delete', { table, ids: seqs, result: r });
588
+ this._runHooks('afterDelete', [table, ids, r]);
589
+ return r;
590
+ }
591
+
592
+ async findByIds(table, ids) {
593
+ await this._flush();
594
+ if (!this._runHooks('beforeFind', [table, { ids }])) return null;
595
+ let r;
596
+ if (this._pkOf(table)) {
597
+ r = ids.map(id => this._findRawById(table, id)).filter(x => x !== null && !(x && x.error));
598
+ } else {
599
+ r = safeJsonParse(wasmBindings.jsql_find_by_ids(table, JSON.stringify(ids)));
600
+ }
601
+ if (r && r.error) throw new Error(r.error);
602
+ this._runHooks('afterFind', [table, { ids }, r]);
603
+ const schema = this._schemas[table];
604
+ if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
605
+ return r;
606
+ }
607
+
608
+ findByIdsRaw(table, ids) {
609
+ return this.findByIds(table, ids);
610
+ }
611
+
612
+ async updateById(table, id, data) {
613
+ if (!this._runHooks('beforeUpdate', [table, id, data])) return;
614
+ await this._flush();
615
+ const seq = this._seqIdByPk(table, id);
616
+ if (seq === null) return null;
617
+ const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(seq), JSON.stringify(data)));
618
+ if (r && r.ok === false) throw new Error(r.error || 'update failed');
619
+ this._emit('update', { table, id, data });
620
+ this._runHooks('afterUpdate', [table, id, data, r]);
621
+ return r;
622
+ }
623
+
624
+ async removeById(table, id) {
625
+ if (!this._runHooks('beforeDelete', [table, id])) return;
626
+ await this._flush();
627
+ const seq = this._seqIdByPk(table, id);
628
+ if (seq === null) return null;
629
+ const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(seq)));
630
+ if (r && r.ok === false) throw new Error(r.error || 'remove failed');
631
+ this._emit('delete', { table, id });
632
+ this._runHooks('afterDelete', [table, id, r]);
633
+ return r;
634
+ }
635
+
636
+ async beginTx() {
637
+ await this._flush();
638
+ const r = safeJsonParse(wasmBindings.jsql_begin_tx());
639
+ if (r && r.ok === false) throw new Error(r.error || 'begin transaction failed');
640
+ this._txId = r.txId;
641
+ return r.txId;
642
+ }
643
+
644
+ async commitTx(txId) {
645
+ const r = safeJsonParse(wasmBindings.jsql_commit_tx(String(txId)));
646
+ if (r && r.ok === false) throw new Error(r.error || 'commit transaction failed');
647
+ return true;
648
+ }
649
+
650
+ async rollbackTx(txId) {
651
+ const r = safeJsonParse(wasmBindings.jsql_rollback_tx(String(txId)));
652
+ if (r && r.ok === false) throw new Error(r.error || 'rollback transaction failed');
653
+ return true;
654
+ }
655
+
656
+ async beginTransaction() {
657
+ return this.beginTx();
658
+ }
659
+
660
+ async commit() {
661
+ if (this._txId === undefined) return true;
662
+ const r = await this.commitTx(this._txId);
663
+ this._txId = undefined;
664
+ return r;
665
+ }
666
+
667
+ async rollback() {
668
+ if (this._txId === undefined) return true;
669
+ const r = await this.rollbackTx(this._txId);
670
+ this._txId = undefined;
671
+ return r;
672
+ }
673
+
674
+ async hasTable(name) {
675
+ return this._tableNames.has(name);
676
+ }
677
+
678
+ async getTables() {
679
+ return Array.from(this._tableNames);
680
+ }
681
+
682
+ async getTableSchema(name) {
683
+ return this._schemas[name] || null;
684
+ }
685
+
686
+ /**
687
+ * 直接执行原生 SQL 语句。本实例自身即充当 engine,
688
+ * 支持 SELECT / INSERT / UPDATE / DELETE / CREATE TABLE 等常用语句。
689
+ * @example
690
+ * const rows = await db.query("SELECT * FROM users WHERE age > ?", [21]);
691
+ */
692
+ async query(sql, paramsOrOpts, opts) {
693
+ if (!this._runHooks('beforeQuery', [sql, paramsOrOpts, opts])) return null;
694
+ const result = await executeSQL(this, sql, paramsOrOpts, opts);
695
+ this._runHooks('afterQuery', [sql, paramsOrOpts, opts, result]);
696
+ return result;
697
+ }
698
+
699
+ async exists(table, filter = {}) {
700
+ const rows = await this.find(table, filter, { limit: 1 });
701
+ return Array.isArray(rows) && rows.length > 0;
702
+ }
703
+
704
+ prepare(sql) {
705
+ return require('./prepared').createPrepared(this, sql);
706
+ }
707
+
708
+ async stop() {
709
+ await this._flush();
710
+ this._runHooks('onStop', []);
711
+ this._emit('stop', {});
712
+ }
713
+ }
714
+
715
+ module.exports = { JSQL };