@maiyunnet/kebab 9.5.0 → 9.7.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/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "9.5.0";
8
+ export declare const VER = "9.7.0";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  /** --- 框架的 LIB,以 / 结尾 --- */
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '9.5.0';
9
+ export const VER = '9.7.0';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
package/lib/core.d.ts CHANGED
@@ -167,12 +167,18 @@ export declare function exec(command: string, options?: {
167
167
  * --- 向主进程(或局域网同代码机子)发送广播将进行 reload 操作,等待回传 ---
168
168
  * --- 主要作用除代码热更新以外的其他情况 ---
169
169
  */
170
- export declare function sendReload(hosts?: string[] | 'config'): Promise<string[]>;
170
+ export declare function sendReload(hosts?: string[] | 'config'): Promise<Record<string, {
171
+ 'result': boolean;
172
+ 'return': string;
173
+ }>>;
171
174
  /**
172
175
  * --- 向主进程(或局域网同代码机子)发送广播将进行 restart 操作,停止监听并启动新进程,老进程在连接全部断开后自行销毁 ---
173
176
  * --- 主要用作不间断的代码热更新 ---
174
177
  */
175
- export declare function sendRestart(hosts?: string[] | 'config'): Promise<string[]>;
178
+ export declare function sendRestart(hosts?: string[] | 'config'): Promise<Record<string, {
179
+ 'result': boolean;
180
+ 'return': string;
181
+ }>>;
176
182
  /** --- PM2 操作类型 --- */
177
183
  export type TPm2Action = 'start' | 'stop' | 'restart';
178
184
  /**
@@ -181,13 +187,19 @@ export type TPm2Action = 'start' | 'stop' | 'restart';
181
187
  * @param action PM2 操作类型
182
188
  * @param hosts 局域网列表
183
189
  */
184
- export declare function sendPm2(name: string, action?: TPm2Action, hosts?: string[] | 'config'): Promise<string[]>;
190
+ export declare function sendPm2(name: string, action?: TPm2Action, hosts?: string[] | 'config'): Promise<Record<string, {
191
+ 'result': boolean;
192
+ 'return': string;
193
+ }>>;
185
194
  /**
186
195
  * --- 向本机或局域网 RPC 发送 npm install 操作 ---
187
196
  * @param path 路径,如 /home/kebab/
188
- * @param hosts 局域网列表
197
+ * @param hosts 局域网列表,不填则代表本机
189
198
  */
190
- export declare function sendNpm(path: string, hosts?: string[] | 'config'): Promise<string[]>;
199
+ export declare function sendNpm(path: string, hosts?: string[] | 'config'): Promise<Record<string, {
200
+ 'result': boolean;
201
+ 'return': string;
202
+ }>>;
191
203
  /** --- 跨进程全局变量 --- */
192
204
  export declare const global: Record<string, any>;
193
205
  /**
@@ -196,13 +208,19 @@ export declare const global: Record<string, any>;
196
208
  * @param data 变量值
197
209
  * @param hosts 局域网列表
198
210
  */
199
- export declare function setGlobal(key: string, data: any, hosts?: string[] | 'config'): Promise<string[]>;
211
+ export declare function setGlobal(key: string, data: any, hosts?: string[] | 'config'): Promise<Record<string, {
212
+ 'result': boolean;
213
+ 'return': string;
214
+ }>>;
200
215
  /**
201
216
  * --- 移除某个跨线程/跨内网服务器全局变量 ---
202
217
  * @param key 变量名
203
218
  * @param hosts 局域网列表
204
219
  */
205
- export declare function removeGlobal(key: string, hosts?: string[]): Promise<string[]>;
220
+ export declare function removeGlobal(key: string, hosts?: string[] | 'config'): Promise<Record<string, {
221
+ 'result': boolean;
222
+ 'return': string;
223
+ }>>;
206
224
  /**
207
225
  * --- 上传并覆盖代码文件,config.json、kebab.json、.js.map、.ts, .gitignore 不会被覆盖和新创建 ---
208
226
  * @param sourcePath zip 文件
@@ -222,13 +240,19 @@ export declare function updateCode(sourcePath: string, path: string, hosts?: str
222
240
  * @param value 要更新的值
223
241
  * @param hosts 局域网列表
224
242
  */
225
- export declare function sendProject(path: string, key: string, value: string, hosts?: string[] | 'config'): Promise<string[]>;
243
+ export declare function sendProject(path: string, key: string, value: string, hosts?: string[] | 'config'): Promise<Record<string, {
244
+ 'result': boolean;
245
+ 'return': string;
246
+ }>>;
226
247
  /**
227
248
  * --- 向本机或局域网 RPC 发送 package.json 更新操作 ---
228
249
  * @param content package.json 文件内容
229
250
  * @param hosts 局域网列表
230
251
  */
231
- export declare function sendPackage(content: string, hosts?: string[] | 'config'): Promise<string[]>;
252
+ export declare function sendPackage(content: string, hosts?: string[] | 'config'): Promise<Record<string, {
253
+ 'result': boolean;
254
+ 'return': string;
255
+ }>>;
232
256
  /** --- log 设置的选项 --- */
233
257
  export interface ILogOptions {
234
258
  'path'?: string;
package/lib/core.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Project: Kebab, User: JianSuoQiYue
3
3
  * Date: 2019-5-3 23:54
4
- * Last: 2020-4-11 22:34:58, 2022-10-2 14:13:06, 2022-12-28 20:33:24, 2023-12-15 11:49:02, 2024-7-2 15:23:35, 2025-6-13 19:45:53
4
+ * Last: 2020-4-11 22:34:58, 2022-10-2 14:13:06, 2022-12-28 20:33:24, 2023-12-15 11:49:02, 2024-7-2 15:23:35, 2025-6-13 19:45:53, 2026-05-20 09:50:00
5
5
  */
6
6
  import * as cp from 'child_process';
7
7
  import * as http2 from 'http2';
@@ -464,7 +464,9 @@ export async function sendReload(hosts) {
464
464
  process.send({
465
465
  'action': 'reload'
466
466
  });
467
- return [];
467
+ return {
468
+ '127.0.0.1': { 'result': true, 'return': 'Done' }
469
+ };
468
470
  }
469
471
  if (hosts === 'config') {
470
472
  hosts = globalConfig.hosts;
@@ -472,7 +474,7 @@ export async function sendReload(hosts) {
472
474
  // --- 局域网模式 ---
473
475
  const time = lTime.stamp();
474
476
  /** --- 返回成功的 host --- */
475
- const rtn = [];
477
+ const rtn = {};
476
478
  for (const host of hosts) {
477
479
  const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
478
480
  'action': 'reload',
@@ -486,7 +488,10 @@ export async function sendReload(hosts) {
486
488
  }
487
489
  const str = content.toString();
488
490
  if (str === 'Done') {
489
- rtn.push(host);
491
+ rtn[host] = { 'result': true, 'return': 'Done' };
492
+ }
493
+ else {
494
+ rtn[host] = { 'result': false, 'return': str };
490
495
  }
491
496
  }
492
497
  return rtn;
@@ -503,7 +508,9 @@ export async function sendRestart(hosts) {
503
508
  process.send({
504
509
  'action': 'restart'
505
510
  });
506
- return [];
511
+ return {
512
+ '127.0.0.1': { 'result': true, 'return': 'Done' }
513
+ };
507
514
  }
508
515
  if (hosts === 'config') {
509
516
  hosts = globalConfig.hosts;
@@ -511,7 +518,7 @@ export async function sendRestart(hosts) {
511
518
  // --- 局域网模式 ---
512
519
  const time = lTime.stamp();
513
520
  /** --- 返回成功的 host --- */
514
- const rtn = [];
521
+ const rtn = {};
515
522
  for (const host of hosts) {
516
523
  const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
517
524
  'action': 'restart',
@@ -525,7 +532,10 @@ export async function sendRestart(hosts) {
525
532
  }
526
533
  const str = content.toString();
527
534
  if (str === 'Done') {
528
- rtn.push(host);
535
+ rtn[host] = { 'result': true, 'return': 'Done' };
536
+ }
537
+ else {
538
+ rtn[host] = { 'result': false, 'return': str };
529
539
  }
530
540
  }
531
541
  return rtn;
@@ -547,7 +557,7 @@ export async function sendPm2(name, action = 'restart', hosts) {
547
557
  // --- 局域网模式 ---
548
558
  const time = lTime.stamp();
549
559
  /** --- 返回成功的 host --- */
550
- const rtn = [];
560
+ const rtn = {};
551
561
  for (const host of hosts) {
552
562
  const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
553
563
  'action': 'pm2',
@@ -563,9 +573,10 @@ export async function sendPm2(name, action = 'restart', hosts) {
563
573
  }
564
574
  const str = content.toString();
565
575
  if (str === 'Done') {
566
- rtn.push(host);
576
+ rtn[host] = { 'result': true, 'return': 'Done' };
567
577
  }
568
578
  else {
579
+ rtn[host] = { 'result': false, 'return': str };
569
580
  debug('[CORE][sendPm2] rpc server content error:', str);
570
581
  }
571
582
  }
@@ -574,7 +585,7 @@ export async function sendPm2(name, action = 'restart', hosts) {
574
585
  /**
575
586
  * --- 向本机或局域网 RPC 发送 npm install 操作 ---
576
587
  * @param path 路径,如 /home/kebab/
577
- * @param hosts 局域网列表
588
+ * @param hosts 局域网列表,不填则代表本机
578
589
  */
579
590
  export async function sendNpm(path, hosts) {
580
591
  if (hosts === 'config') {
@@ -587,7 +598,7 @@ export async function sendNpm(path, hosts) {
587
598
  // --- 局域网模式 ---
588
599
  const time = lTime.stamp();
589
600
  /** --- 返回成功的 host --- */
590
- const rtn = [];
601
+ const rtn = {};
591
602
  for (const host of hosts) {
592
603
  const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
593
604
  'action': 'npm',
@@ -602,9 +613,10 @@ export async function sendNpm(path, hosts) {
602
613
  }
603
614
  const str = content.toString();
604
615
  if (str === 'Done') {
605
- rtn.push(host);
616
+ rtn[host] = { 'result': true, 'return': 'Done' };
606
617
  }
607
618
  else {
619
+ rtn[host] = { 'result': false, 'return': str };
608
620
  debug('[CORE][sendNpmInstall] rpc server content error:', str);
609
621
  }
610
622
  }
@@ -626,7 +638,9 @@ export async function setGlobal(key, data, hosts) {
626
638
  'key': key,
627
639
  'data': data
628
640
  });
629
- return [];
641
+ return {
642
+ '127.0.0.1': { 'result': true, 'return': 'Done' }
643
+ };
630
644
  }
631
645
  if (hosts === 'config') {
632
646
  hosts = globalConfig.hosts;
@@ -634,7 +648,7 @@ export async function setGlobal(key, data, hosts) {
634
648
  // --- 局域网模式 ---
635
649
  const time = lTime.stamp();
636
650
  /** --- 返回成功的 host --- */
637
- const rtn = [];
651
+ const rtn = {};
638
652
  for (const host of hosts) {
639
653
  const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
640
654
  'action': 'global',
@@ -648,7 +662,10 @@ export async function setGlobal(key, data, hosts) {
648
662
  }
649
663
  const str = content.toString();
650
664
  if (str === 'Done') {
651
- rtn.push(host);
665
+ rtn[host] = { 'result': true, 'return': 'Done' };
666
+ }
667
+ else {
668
+ rtn[host] = { 'result': false, 'return': str };
652
669
  }
653
670
  }
654
671
  return rtn;
@@ -730,7 +747,7 @@ export async function sendProject(path, key, value, hosts) {
730
747
  // --- 局域网模式 ---
731
748
  const time = lTime.stamp();
732
749
  /** --- 返回成功的 host --- */
733
- const rtn = [];
750
+ const rtn = {};
734
751
  for (const host of hosts) {
735
752
  const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
736
753
  'action': 'project',
@@ -746,9 +763,10 @@ export async function sendProject(path, key, value, hosts) {
746
763
  }
747
764
  const str = content.toString();
748
765
  if (str === 'Done') {
749
- rtn.push(host);
766
+ rtn[host] = { 'result': true, 'return': 'Done' };
750
767
  }
751
768
  else {
769
+ rtn[host] = { 'result': false, 'return': str };
752
770
  debug('[CORE][sendProject] rpc server content error:', str);
753
771
  }
754
772
  }
@@ -770,7 +788,7 @@ export async function sendPackage(content, hosts) {
770
788
  // --- 局域网模式 ---
771
789
  const time = lTime.stamp();
772
790
  /** --- 返回成功的 host --- */
773
- const rtn = [];
791
+ const rtn = {};
774
792
  for (const host of hosts) {
775
793
  const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
776
794
  'action': 'package',
@@ -785,9 +803,10 @@ export async function sendPackage(content, hosts) {
785
803
  }
786
804
  const str = resContent.toString();
787
805
  if (str === 'Done') {
788
- rtn.push(host);
806
+ rtn[host] = { 'result': true, 'return': 'Done' };
789
807
  }
790
808
  else {
809
+ rtn[host] = { 'result': false, 'return': str };
791
810
  debug('[CORE][sendPackage] rpc server content error:', str);
792
811
  }
793
812
  }
package/lib/sql.js CHANGED
@@ -182,21 +182,24 @@ export class Sql {
182
182
  const quotedTable = this.field(table, this._pre);
183
183
  const quotedKey = this.field(key);
184
184
  if (this._service === ESERVICE.MYSQL) {
185
- // --- MySQL 8.0.19+ VALUES ROW() 派生表语法 ---
186
- const valueParts = [];
187
- for (const row of rows) {
188
- const parts = row.map(v => {
185
+ // --- MySQL 使用 UNION ALL SELECT 子查询,比 VALUES ROW() 更稳定 ---
186
+ // --- MySQL 8.0 优化器在行数较多时对 VALUES ROW() 派生表会走不同执行路径,
187
+ // 导致列名别名解析失败,JOIN 静默匹配 0 行;UNION ALL 无此问题 ---
188
+ const selectParts = [];
189
+ for (let ri = 0; ri < rows.length; ri++) {
190
+ const row = rows[ri];
191
+ const parts = row.map((v, ci) => {
189
192
  const result = this._processValue(v);
190
193
  if (result.data.length > 0) {
191
194
  this._data.push(...result.data);
192
195
  }
193
- return result.sql;
196
+ // --- 第一行加列名别名,后续行省略 ---
197
+ return ri === 0 ? `${result.sql} AS ${this.field(allCols[ci])}` : result.sql;
194
198
  });
195
- valueParts.push(`ROW(${parts.join(', ')})`);
199
+ selectParts.push(`SELECT ${parts.join(', ')}`);
196
200
  }
197
- const tmpCols = allCols.map(c => this.field(c)).join(', ');
198
201
  const setClauses = cols.map(c => `t.${this.field(c)} = tmp.${this.field(c)}`).join(', ');
199
- this._sql = [`UPDATE ${quotedTable} t INNER JOIN (VALUES ${valueParts.join(', ')}) AS tmp(${tmpCols}) ON t.${quotedKey} = tmp.${quotedKey} SET ${setClauses}`];
202
+ this._sql = [`UPDATE ${quotedTable} t INNER JOIN (${selectParts.join(' UNION ALL ')}) AS tmp ON t.${quotedKey} = tmp.${quotedKey} SET ${setClauses}`];
200
203
  }
201
204
  else {
202
205
  // --- PostgreSQL 使用 UPDATE FROM (VALUES ...) ---
@@ -596,8 +599,18 @@ export class Sql {
596
599
  // --- 3 ---
597
600
  sql += this.field(v[0]) + ' ' + v[1].toUpperCase() + ' (';
598
601
  for (const v1 of v[2]) {
599
- sql += this._placeholder() + ', ';
600
- data.push(v1);
602
+ if (Array.isArray(v1)) {
603
+ sql += '(';
604
+ for (const v2 of v1) {
605
+ sql += this._placeholder() + ', ';
606
+ data.push(v2);
607
+ }
608
+ sql = sql.slice(0, -2) + '), ';
609
+ }
610
+ else {
611
+ sql += this._placeholder() + ', ';
612
+ data.push(v1);
613
+ }
601
614
  }
602
615
  sql = sql.slice(0, -2) + ') AND ';
603
616
  }
@@ -658,8 +671,18 @@ export class Sql {
658
671
  if (v.length > 0) {
659
672
  sql += this.field(k) + ' IN (';
660
673
  for (const v1 of v) {
661
- sql += this._placeholder() + ', ';
662
- data.push(v1);
674
+ if (Array.isArray(v1)) {
675
+ sql += '(';
676
+ for (const v2 of v1) {
677
+ sql += this._placeholder() + ', ';
678
+ data.push(v2);
679
+ }
680
+ sql = sql.slice(0, -2) + '), ';
681
+ }
682
+ else {
683
+ sql += this._placeholder() + ', ';
684
+ data.push(v1);
685
+ }
663
686
  }
664
687
  sql = sql.slice(0, -2) + ') AND ';
665
688
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "9.5.0",
3
+ "version": "9.7.0",
4
4
  "description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
5
5
  "type": "module",
6
6
  "keywords": [
package/sys/master.js CHANGED
@@ -404,7 +404,7 @@ function createRpcListener() {
404
404
  // --- 特殊文件不能覆盖 ---
405
405
  continue;
406
406
  }
407
- if (fname.endsWith('.js.map') || fname.endsWith('.ts') || fname.endsWith('.scss') || fname.endsWith('.gitignore') || fname.endsWith('.DS_Store')) {
407
+ if (fname.endsWith('.js.map') || fname.endsWith('.ts') || fname.endsWith('.tsx') || fname.endsWith('.scss') || fname.endsWith('.gitignore') || fname.endsWith('.DS_Store')) {
408
408
  // --- 测试或开发文件不覆盖 ---
409
409
  continue;
410
410
  }
package/sys/mod.d.ts CHANGED
@@ -180,8 +180,10 @@ export default class Mod {
180
180
  /**
181
181
  * --- 批量更新数据 ---
182
182
  * @param db 数据库对象
183
- * @param data 数据列表
184
- * @param key 用于定位的主键或唯一键字段名
183
+ * @param data 数据列表,每个元素必须包含 key 字段,其余字段为要更新的列;
184
+ * 支持稀疏数据(不同元素可以拥有不同的列集合),内部会自动按列集合分组批量执行
185
+ * @param key 用于定位记录的字段名(主键或唯一键),该字段仅用于 WHERE 条件匹配,不会被更新;
186
+ * data 中每个元素都必须包含此字段,否则该元素会被跳过
185
187
  * @param opt 选项(opt.pre: MySQL 表前缀/PostgreSQL Schema 名)
186
188
  */
187
189
  static updateList(db: lDb.Pool | lDb.Transaction, data: Array<Record<string, any>>, key: string, opt?: {
package/sys/mod.js CHANGED
@@ -305,8 +305,10 @@ export default class Mod {
305
305
  /**
306
306
  * --- 批量更新数据 ---
307
307
  * @param db 数据库对象
308
- * @param data 数据列表
309
- * @param key 用于定位的主键或唯一键字段名
308
+ * @param data 数据列表,每个元素必须包含 key 字段,其余字段为要更新的列;
309
+ * 支持稀疏数据(不同元素可以拥有不同的列集合),内部会自动按列集合分组批量执行
310
+ * @param key 用于定位记录的字段名(主键或唯一键),该字段仅用于 WHERE 条件匹配,不会被更新;
311
+ * data 中每个元素都必须包含此字段,否则该元素会被跳过
310
312
  * @param opt 选项(opt.pre: MySQL 表前缀/PostgreSQL Schema 名)
311
313
  */
312
314
  static async updateList(db, data, key, opt = {}) {
@@ -1407,7 +1407,10 @@ for (let i = 0; i < 30000; ++i) {
1407
1407
  else {
1408
1408
  // --- jsonl 格式:object ---
1409
1409
  const r = row;
1410
- for (const val of [r.time, r.unix, r.url, r.cookie, r.session, r.userAgent, r.realIp, r.cfIp, r.xIp, r.osMem, r.procMem, r.message]) {
1410
+ for (const val of [
1411
+ r.time, r.unix, r.url,
1412
+ r.cookie, r.session, r.userAgent, r.realIp, r.cfIp, r.xIp, r.osMem, r.procMem, r.message
1413
+ ]) {
1411
1414
  echo.push('<td>' + lText.htmlescape(typeof val === 'string' ? val : lText.stringifyJson(val) ?? '') + '</td>');
1412
1415
  }
1413
1416
  }
@@ -1466,12 +1469,12 @@ to: ${to}`
1466
1469
  return echo.join('') + '<br><br>' + this._getEnd();
1467
1470
  }
1468
1471
  async coreReload() {
1469
- await lCore.sendReload();
1470
- return 'The reload request has been sent, please review the console.<br><br>' + this._getEnd();
1472
+ const list = await lCore.sendReload();
1473
+ return `The reload request has been sent, please review the console.<br>Hosts: ${JSON.stringify(list)}<br><br>` + this._getEnd();
1471
1474
  }
1472
1475
  async coreRestart() {
1473
- await lCore.sendRestart();
1474
- return 'The restart request has been sent, please review the console.<br><br>' + this._getEnd();
1476
+ const list = await lCore.sendRestart();
1477
+ return `The restart request has been sent, please review the console.<br>Hosts: ${JSON.stringify(list)}<br><br>` + this._getEnd();
1475
1478
  }
1476
1479
  async corePm2() {
1477
1480
  const name = this._get['name'] ?? '';
@@ -1483,7 +1486,7 @@ to: ${to}`
1483
1486
  return 'Invalid action. Must be: start, stop, restart<br><br>' + this._getEnd();
1484
1487
  }
1485
1488
  const list = await lCore.sendPm2(name, action);
1486
- return `PM2 ${action} request has been sent for "${name}".<br>Success hosts: ${JSON.stringify(list)}<br><br>` + this._getEnd();
1489
+ return `PM2 ${action} request has been sent for "${name}".<br>Hosts: ${JSON.stringify(list)}<br><br>` + this._getEnd();
1487
1490
  }
1488
1491
  async coreNpm() {
1489
1492
  // --- 创建一个临时目录 ---
@@ -1498,7 +1501,7 @@ to: ${to}`
1498
1501
  }
1499
1502
  }));
1500
1503
  const list = await lCore.sendNpm(path);
1501
- return `NPM request has been sent.<br>Success hosts: ${JSON.stringify(list)}<br><br>` + this._getEnd();
1504
+ return `NPM request has been sent.<br>Hosts: ${JSON.stringify(list)}<br><br>` + this._getEnd();
1502
1505
  }
1503
1506
  async coreGlobal() {
1504
1507
  const ts = lTime.stamp().toString();
@@ -3402,12 +3405,14 @@ Result:<pre id="result">Nothing.</pre>`);
3402
3405
  s = sql.update('order', { 'state': '1' }).where({
3403
3406
  'user_id': '2',
3404
3407
  'state': ['1', '2', '3'],
3408
+ '(a, b, c)': [['1', '2', '3'], ['4', '5', '6']],
3405
3409
  '$or': [{ 'type': '1', 'find': '0' }, { 'type': '2', 'find': '1' }, [['type', '<', '-1']]]
3406
3410
  }).getSql();
3407
3411
  sd = sql.getData();
3408
3412
  echo.push(`<pre>sql.update('order', { 'state': '1' }).where({
3409
3413
  'user_id': '2',
3410
3414
  'state': ['1', '2', '3'],
3415
+ '(a, b, c)': [['1', '2', '3'], ['4', '5', '6']],
3411
3416
  '$or': [{ 'type': '1', 'find': '0' }, { 'type': '2', 'find': '1' }, [['type', '<', '-1']]]
3412
3417
  });</pre>
3413
3418
  <b>getSql() :</b> ${s}<br>