jsql-neo 5.3.1 → 5.4.1

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 (75) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +240 -33
  3. package/bin/jsql +0 -0
  4. package/bin/jsql-server +0 -0
  5. package/bin/jsql-server-control.js +0 -0
  6. package/index.d.ts +0 -0
  7. package/index.js +0 -0
  8. package/lib/btree.js +0 -0
  9. package/lib/cache.js +0 -0
  10. package/lib/client.js +0 -0
  11. package/lib/database.js +0 -0
  12. package/lib/date-types.js +0 -0
  13. package/lib/errors.js +0 -0
  14. package/lib/jsql_format.js +0 -0
  15. package/lib/migrate.js +0 -0
  16. package/lib/mod.js +0 -0
  17. package/lib/mongo_server.js +1 -1
  18. package/lib/multiserver.js +0 -0
  19. package/lib/mysql_compat.js +0 -0
  20. package/lib/mysql_server.js +0 -0
  21. package/lib/native_client.js +182 -23
  22. package/lib/nedb_compat.js +0 -0
  23. package/lib/pg_server.js +0 -0
  24. package/lib/plugin.js +0 -0
  25. package/lib/query.js +0 -0
  26. package/lib/redis_server.js +0 -0
  27. package/lib/sql.js +0 -0
  28. package/lib/sqlite_compat.js +0 -0
  29. package/lib/sqlite_worker.js +5 -1
  30. package/lib/table.js +0 -0
  31. package/lib/tui.js +0 -0
  32. package/lib/wasm.js +0 -0
  33. package/lib/wasm_client.js +200 -23
  34. package/lib/web_ui.js +1 -1
  35. package/nativesrc/jsql-neo-core/Cargo.lock +0 -0
  36. package/nativesrc/jsql-neo-core/Cargo.toml +1 -1
  37. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +0 -0
  38. package/nativesrc/jsql-neo-core/src/engine/memory.rs +0 -0
  39. package/nativesrc/jsql-neo-core/src/engine/mod.rs +0 -0
  40. package/nativesrc/jsql-neo-core/src/engine/table.rs +0 -0
  41. package/nativesrc/jsql-neo-core/src/lib.rs +0 -0
  42. package/nativesrc/jsql-neo-core/src/storage/mod.rs +0 -0
  43. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +0 -0
  44. package/nativesrc/jsql-neo-core/src/storage/wal.rs +0 -0
  45. package/nativesrc/jsql-neo-core/src/types.rs +0 -0
  46. package/nativesrc/jsql-neo-native/Cargo.lock +0 -0
  47. package/nativesrc/jsql-neo-native/Cargo.toml +1 -1
  48. package/nativesrc/jsql-neo-native/build.rs +0 -0
  49. package/nativesrc/jsql-neo-native/jsql-neo-native.node +0 -0
  50. package/nativesrc/jsql-neo-native/package.json +0 -0
  51. package/nativesrc/jsql-neo-native/src/lib.rs +0 -0
  52. package/nativesrc/jsql-neo-wasm/Cargo.lock +2 -2
  53. package/nativesrc/jsql-neo-wasm/Cargo.toml +1 -1
  54. package/nativesrc/jsql-neo-wasm/src/lib.rs +24 -0
  55. package/package.json +3 -3
  56. package/postinstall.js +146 -25
  57. package/test/btree.test.js +0 -0
  58. package/test/coverage.js +0 -0
  59. package/test/join.test.js +110 -0
  60. package/test/native.test.js +159 -0
  61. package/test/regress-5.1.0.js +0 -0
  62. package/test/smoke.js +0 -0
  63. package/test/wasm.test.js +111 -0
  64. package/wasm/browser.d.ts +81 -80
  65. package/wasm/browser.mjs +407 -242
  66. package/wasm/browser_bg.mjs +166 -31
  67. package/wasm/jsql_neo_wasm.d.ts +6 -0
  68. package/wasm/jsql_neo_wasm.js +57 -0
  69. package/wasm/jsql_neo_wasm_bg.wasm +0 -0
  70. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +3 -0
  71. package/wasm/package.json +1 -1
  72. package/bin/jsql-neo-server +0 -0
  73. package/lib/compress_pool.js +0 -122
  74. package/lib/compress_worker.js +0 -32
  75. package/wasm/jsql_neo_wasm_bg.js +0 -373
@@ -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');
File without changes
package/lib/pg_server.js CHANGED
File without changes
package/lib/plugin.js CHANGED
File without changes
package/lib/query.js CHANGED
File without changes
File without changes
package/lib/sql.js CHANGED
File without changes
File without changes
@@ -20,7 +20,7 @@ function startEngine(filename, options) {
20
20
  const opts = options || {};
21
21
  const isMemory = filename === ':memory:' || !filename;
22
22
  if (opts.engine === 'native') {
23
- const { NativeJSQL } = require('./native_client');
23
+ const { JSQL: NativeJSQL } = require('./native_client');
24
24
  const engine = new NativeJSQL({ mode: 'memory' });
25
25
  db = {
26
26
  start: async () => { await engine.start(); },
@@ -30,10 +30,14 @@ function startEngine(filename, options) {
30
30
  dropTable: n => engine.dropTable(n),
31
31
  insert: (n, rows) => engine.insert(n, rows),
32
32
  find: (n, f, o) => engine.find(n, f, o),
33
+ updateById: (n, id, data) => engine.updateById(n, id, data),
33
34
  updateByIds: (n, e) => engine.updateByIds(n, e),
34
35
  removeByIds: (n, ids) => engine.removeByIds(n, ids),
35
36
  count: (n, f) => engine.count ? engine.count(n, f) : engine.find(n, f || {}, {}).then(rs => rs.length),
36
37
  getTableSchema: n => engine._schemas[n] || null,
38
+ beginTx: () => engine.beginTx(),
39
+ commitTx: id => engine.commitTx(id),
40
+ rollbackTx: id => engine.rollbackTx(id),
37
41
  truncate: async () => { throw new Error('truncate not supported on native engine'); },
38
42
  flush: async () => { if (engine.flush) await engine.flush(); },
39
43
  listTables: () => Array.from(engine._tableNames || []),
package/lib/table.js CHANGED
File without changes
package/lib/tui.js CHANGED
File without changes
package/lib/wasm.js CHANGED
File without changes
@@ -23,14 +23,52 @@ const NATIVE_TYPE_MAP = {
23
23
  int: 'integer',
24
24
  };
25
25
 
26
+ function parseShorthandSchema(str) {
27
+ const def = { type: str };
28
+ const strip = (re) => {
29
+ const next = def.type.replace(re, '').trim();
30
+ def.type = next || def.type;
31
+ };
32
+ if (/\bprimary\s+key\b/i.test(def.type)) { def.primaryKey = true; strip(/\bprimary\s+key\b/gi); }
33
+ if (/\bauto_?increment\b/i.test(def.type)) { def.autoIncrement = true; strip(/\bauto_?increment\b/gi); }
34
+ if (/\bnot\s+null\b/i.test(def.type)) { def.required = true; strip(/\bnot\s+null\b/gi); }
35
+ if (/\bunique\b/i.test(def.type)) { def.unique = true; strip(/\bunique\b/gi); }
36
+ if (/\bdefault\s+(\S+)/i.test(def.type)) {
37
+ const m = def.type.match(/\bdefault\s+(\S+)/i);
38
+ def.default = m[1].replace(/^['"]|['"]$/g, '');
39
+ strip(/\bdefault\s+\S+/gi);
40
+ }
41
+ return def;
42
+ }
43
+
44
+ function parseSchemaString(str) {
45
+ const out = {};
46
+ for (const seg of str.split(',')) {
47
+ const trimmed = seg.trim();
48
+ if (!trimmed) continue;
49
+ const parts = trimmed.split(/\s+/);
50
+ const name = parts.shift();
51
+ const def = parseShorthandSchema(parts.join(' ').toLowerCase().trim());
52
+ if (def.type.length === 0) def.type = 'string';
53
+ out[name] = def;
54
+ }
55
+ return out;
56
+ }
57
+
26
58
  function mapNativeSchema(schema) {
59
+ if (typeof schema === 'string') schema = parseSchemaString(schema);
27
60
  const mapped = {};
28
61
  for (const [field, def] of Object.entries(schema || {})) {
29
62
  if (typeof def === 'string') {
30
- mapped[field] = { type: NATIVE_TYPE_MAP[def.toLowerCase()] || def.toLowerCase() };
63
+ const parsed = parseShorthandSchema(def.toLowerCase().trim());
64
+ if (parsed.type.length === 0) parsed.type = 'string';
65
+ mapped[field] = { ...parsed, type: NATIVE_TYPE_MAP[parsed.type] || parsed.type };
31
66
  } else if (def && typeof def === 'object') {
32
67
  const t = (def.type || 'string').toLowerCase();
33
- mapped[field] = { ...def, type: NATIVE_TYPE_MAP[t] || t };
68
+ const out = { ...def, type: NATIVE_TYPE_MAP[t] || t };
69
+ if (out.primary_key !== undefined && out.primaryKey === undefined) { out.primaryKey = out.primary_key; delete out.primary_key; }
70
+ if (out.auto_increment !== undefined && out.autoIncrement === undefined) { out.autoIncrement = out.auto_increment; delete out.auto_increment; }
71
+ mapped[field] = out;
34
72
  } else {
35
73
  mapped[field] = { type: 'string' };
36
74
  }
@@ -39,25 +77,46 @@ function mapNativeSchema(schema) {
39
77
  }
40
78
 
41
79
  function restoreRow(row, schema) {
42
- if (!schema || typeof row !== 'object' || row === null) return row;
43
- const out = {};
44
- for (const [field, def] of Object.entries(row)) {
45
- if (field === 'fields' && typeof row[field] === 'object' && row[field] !== null) {
46
- out[field] = restoreRow(row[field], schema);
47
- continue;
80
+ if (typeof row !== 'object' || row === null) return row;
81
+ let src = row;
82
+ if (row.fields && typeof row.fields === 'object' && row.fields !== null) {
83
+ src = { ...row.fields };
84
+ if (row.id !== undefined) {
85
+ const pkCols = [];
86
+ if (schema) {
87
+ for (const [k, def] of Object.entries(schema)) {
88
+ if (def && typeof def === 'object' && def.autoIncrement) continue;
89
+ const isPk = typeof def === 'string'
90
+ ? /\bprimary\s+key\b/i.test(def)
91
+ : !!(def && (def.primaryKey || def.primary_key));
92
+ if (isPk) pkCols.push(k);
93
+ }
94
+ } else if (src.id === undefined) {
95
+ pkCols.push('id');
96
+ }
97
+ for (const c of pkCols) {
98
+ const v = src[c];
99
+ if (v === undefined || v === null || v === 0 || v === '' || v === false) src[c] = row.id;
100
+ }
48
101
  }
102
+ if (row.created_at !== undefined) src.created_at = row.created_at;
103
+ if (row.updated_at !== undefined) src.updated_at = row.updated_at;
104
+ }
105
+ if (!schema) return src;
106
+ const out = {};
107
+ for (const [field, value] of Object.entries(src)) {
49
108
  const colDef = schema[field];
50
109
  let t = null;
51
110
  if (typeof colDef === 'string') t = colDef.toLowerCase();
52
111
  else if (colDef && typeof colDef === 'object' && colDef.type) t = String(colDef.type).toLowerCase();
53
- if (['json', 'array', 'object'].includes(t) && typeof row[field] === 'string') {
54
- try { out[field] = JSON.parse(row[field]); } catch (e) { out[field] = row[field]; }
55
- } else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof row[field] === 'string' && /^-?\d+$/.test(row[field])) {
56
- out[field] = Number(row[field]);
57
- } else if ((t === 'float' || t === 'double' || t === 'number') && typeof row[field] === 'string' && !Number.isNaN(Number(row[field]))) {
58
- out[field] = Number(row[field]);
112
+ if (['json', 'array', 'object'].includes(t) && typeof value === 'string') {
113
+ try { out[field] = JSON.parse(value); } catch (e) { out[field] = value; }
114
+ } else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof value === 'string' && /^-?\d+$/.test(value)) {
115
+ out[field] = Number(value);
116
+ } else if ((t === 'float' || t === 'double' || t === 'number') && typeof value === 'string' && !Number.isNaN(Number(value))) {
117
+ out[field] = Number(value);
59
118
  } else {
60
- out[field] = row[field];
119
+ out[field] = value;
61
120
  }
62
121
  }
63
122
  return out;
@@ -230,6 +289,7 @@ class JSQL {
230
289
  this._bufferSize = 0;
231
290
  this._tableNames = new Set();
232
291
  this._schemas = {};
292
+ this._txId = undefined;
233
293
  this._autoPlugins = opts.modules !== false;
234
294
 
235
295
  this._plugins = [];
@@ -324,6 +384,62 @@ class JSQL {
324
384
  this._emit('start', {});
325
385
  }
326
386
 
387
+ _pkOf(table) {
388
+ const schema = this._schemas[table];
389
+ if (!schema) return null;
390
+ for (const [k, def] of Object.entries(schema)) {
391
+ const isPk = (typeof def === 'string' && /\bprimary\s+key\b/i.test(def))
392
+ || !!(def && typeof def === 'object' && (def.primaryKey || def.primary_key));
393
+ if (isPk) return k;
394
+ }
395
+ return null;
396
+ }
397
+
398
+ _pkAutoIncrement(table) {
399
+ const schema = this._schemas[table];
400
+ const pk = this._pkOf(table);
401
+ if (!pk || !schema) return false;
402
+ const def = schema[pk];
403
+ if (typeof def === 'string') return /\bauto_?increment\b/i.test(def);
404
+ return !!(def && def.autoIncrement);
405
+ }
406
+
407
+ _coercePkId(table, id) {
408
+ const schema = this._schemas[table];
409
+ const pk = this._pkOf(table);
410
+ if (!schema || !pk) return id;
411
+ const def = schema[pk];
412
+ let t = null;
413
+ if (typeof def === 'string') t = def.toLowerCase();
414
+ else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
415
+ if (['integer', 'int', 'bigint'].includes(t)) return Number(id);
416
+ return id;
417
+ }
418
+
419
+ _findRawById(table, id) {
420
+ const pk = this._pkOf(table);
421
+ if (pk) {
422
+ const filter = {};
423
+ filter[pk] = this._coercePkId(table, id);
424
+ const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
425
+ if (r && r.error) return r;
426
+ return Array.isArray(r) && r.length > 0 ? r[0] : null;
427
+ }
428
+ return safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
429
+ }
430
+
431
+ _seqIdByPk(table, id) {
432
+ const pk = this._pkOf(table);
433
+ if (pk) {
434
+ const filter = {};
435
+ filter[pk] = this._coercePkId(table, id);
436
+ const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
437
+ if (Array.isArray(r) && r.length > 0) return r[0].id;
438
+ return null;
439
+ }
440
+ return Number(id);
441
+ }
442
+
327
443
  async _insertBatch(table, rows) {
328
444
  const schema = this._schemas[table];
329
445
  if (schema) {
@@ -347,6 +463,15 @@ class JSQL {
347
463
  }
348
464
  const r = safeJsonParse(wasmBindings.jsql_insert_json(table, JSON.stringify(rows)));
349
465
  if (r && r.error) throw new Error(r.error);
466
+ if (r && Array.isArray(r) && !this._pkAutoIncrement(table)) {
467
+ const pk = this._pkOf(table);
468
+ if (pk) {
469
+ return r.map((seq, i) => {
470
+ const row = rows[i];
471
+ return (row && row[pk] !== undefined && row[pk] !== null) ? row[pk] : seq;
472
+ });
473
+ }
474
+ }
350
475
  return r;
351
476
  }
352
477
 
@@ -401,7 +526,7 @@ class JSQL {
401
526
  const r = safeJsonParse(wasmBindings.jsql_create_table(name, JSON.stringify(mapNativeSchema(schema))));
402
527
  if (r && r.ok === false) throw new Error(r.error || 'create table failed');
403
528
  this._tableNames.add(name);
404
- this._schemas[name] = schema;
529
+ this._schemas[name] = typeof schema === 'string' ? mapNativeSchema(schema) : schema;
405
530
  this._emit('createTable', { name, schema });
406
531
  this._runHooks('afterCreateTable', [name, schema]);
407
532
  return r;
@@ -422,7 +547,7 @@ class JSQL {
422
547
  async findById(table, id) {
423
548
  await this._flush();
424
549
  if (!this._runHooks('beforeFind', [table, { id }])) return null;
425
- const r = safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
550
+ const r = this._findRawById(table, id);
426
551
  if (r && r.error) throw new Error(r.error);
427
552
  this._runHooks('afterFind', [table, { id }, r]);
428
553
  const schema = this._schemas[table];
@@ -453,7 +578,11 @@ class JSQL {
453
578
  }
454
579
 
455
580
  async updateByIds(table, entries) {
456
- const pairs = entries.map(([id, data]) => [Number(id), data]);
581
+ const pairs = [];
582
+ for (const [id, data] of entries) {
583
+ const seq = this._seqIdByPk(table, id);
584
+ if (seq !== null) pairs.push([seq, data]);
585
+ }
457
586
  if (!this._runHooks('beforeUpdate', [table, pairs])) return;
458
587
  await this._flush();
459
588
  const r = safeJsonParse(wasmBindings.jsql_update_by_ids(table, JSON.stringify(pairs)));
@@ -466,9 +595,10 @@ class JSQL {
466
595
  async removeByIds(table, ids) {
467
596
  if (!this._runHooks('beforeDelete', [table, ids])) return;
468
597
  await this._flush();
469
- const r = safeJsonParse(wasmBindings.jsql_remove_by_ids(table, JSON.stringify(ids)));
598
+ const seqs = ids.map(id => this._seqIdByPk(table, id)).filter(s => s !== null);
599
+ const r = safeJsonParse(wasmBindings.jsql_remove_by_ids(table, JSON.stringify(seqs)));
470
600
  if (r && r.error) throw new Error(r.error);
471
- this._emit('delete', { table, ids, result: r });
601
+ this._emit('delete', { table, ids: seqs, result: r });
472
602
  this._runHooks('afterDelete', [table, ids, r]);
473
603
  return r;
474
604
  }
@@ -476,7 +606,12 @@ class JSQL {
476
606
  async findByIds(table, ids) {
477
607
  await this._flush();
478
608
  if (!this._runHooks('beforeFind', [table, { ids }])) return null;
479
- const r = safeJsonParse(wasmBindings.jsql_find_by_ids(table, JSON.stringify(ids)));
609
+ let r;
610
+ if (this._pkOf(table)) {
611
+ r = ids.map(id => this._findRawById(table, id)).filter(x => x !== null && !(x && x.error));
612
+ } else {
613
+ r = safeJsonParse(wasmBindings.jsql_find_by_ids(table, JSON.stringify(ids)));
614
+ }
480
615
  if (r && r.error) throw new Error(r.error);
481
616
  this._runHooks('afterFind', [table, { ids }, r]);
482
617
  const schema = this._schemas[table];
@@ -491,7 +626,9 @@ class JSQL {
491
626
  async updateById(table, id, data) {
492
627
  if (!this._runHooks('beforeUpdate', [table, id, data])) return;
493
628
  await this._flush();
494
- const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(id), JSON.stringify(data)));
629
+ const seq = this._seqIdByPk(table, id);
630
+ if (seq === null) return null;
631
+ const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(seq), JSON.stringify(data)));
495
632
  if (r && r.ok === false) throw new Error(r.error || 'update failed');
496
633
  this._emit('update', { table, id, data });
497
634
  this._runHooks('afterUpdate', [table, id, data, r]);
@@ -501,13 +638,53 @@ class JSQL {
501
638
  async removeById(table, id) {
502
639
  if (!this._runHooks('beforeDelete', [table, id])) return;
503
640
  await this._flush();
504
- const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(id)));
641
+ const seq = this._seqIdByPk(table, id);
642
+ if (seq === null) return null;
643
+ const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(seq)));
505
644
  if (r && r.ok === false) throw new Error(r.error || 'remove failed');
506
645
  this._emit('delete', { table, id });
507
646
  this._runHooks('afterDelete', [table, id, r]);
508
647
  return r;
509
648
  }
510
649
 
650
+ async beginTx() {
651
+ await this._flush();
652
+ const r = safeJsonParse(wasmBindings.jsql_begin_tx());
653
+ if (r && r.ok === false) throw new Error(r.error || 'begin transaction failed');
654
+ this._txId = r.txId;
655
+ return r.txId;
656
+ }
657
+
658
+ async commitTx(txId) {
659
+ const r = safeJsonParse(wasmBindings.jsql_commit_tx(String(txId)));
660
+ if (r && r.ok === false) throw new Error(r.error || 'commit transaction failed');
661
+ return true;
662
+ }
663
+
664
+ async rollbackTx(txId) {
665
+ const r = safeJsonParse(wasmBindings.jsql_rollback_tx(String(txId)));
666
+ if (r && r.ok === false) throw new Error(r.error || 'rollback transaction failed');
667
+ return true;
668
+ }
669
+
670
+ async beginTransaction() {
671
+ return this.beginTx();
672
+ }
673
+
674
+ async commit() {
675
+ if (this._txId === undefined) return true;
676
+ const r = await this.commitTx(this._txId);
677
+ this._txId = undefined;
678
+ return r;
679
+ }
680
+
681
+ async rollback() {
682
+ if (this._txId === undefined) return true;
683
+ const r = await this.rollbackTx(this._txId);
684
+ this._txId = undefined;
685
+ return r;
686
+ }
687
+
511
688
  async hasTable(name) {
512
689
  return this._tableNames.has(name);
513
690
  }