@maiyunnet/kebab 9.2.7 → 9.3.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.2.7";
8
+ export declare const VER = "9.3.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.2.7';
9
+ export const VER = '9.3.0';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
package/lib/s3.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Project: Kebab, User: Tang Rukun, JianSuoQiYue
2
+ * Project: Kebab, User: JianSuoQiYue
3
3
  * Date: 2024-2-18 18:32:45
4
4
  * Last: 2024-2-18 18:32:47, 2024-3-16 16:42:27, 2024-5-31 21:36:26, 2024-7-8 00:28:42, 2024-7-19 11:32:43, 2025-6-10 21:45:34, 2025-12-5 23:42:59
5
5
  */
package/lib/s3.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Project: Kebab, User: Tang Rukun, JianSuoQiYue
2
+ * Project: Kebab, User: JianSuoQiYue
3
3
  * Date: 2024-2-18 18:32:45
4
4
  * Last: 2024-2-18 18:32:47, 2024-3-16 16:42:27, 2024-5-31 21:36:26, 2024-7-8 00:28:42, 2024-7-19 11:32:43, 2025-6-10 21:45:34, 2025-12-5 23:42:59
5
5
  */
package/lib/sql.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Project: Kebab, User: JianSuoQiYue
3
3
  * Date: 2019-5-27 20:18:50
4
- * Last: 2020-3-29 19:37:25, 2022-07-24 22:38:11, 2023-5-24 18:49:18, 2023-6-13 22:20:21, 2023-12-11 13:58:54, 2023-12-14 13:14:40, 2023-12-21 00:04:40, 2024-4-11 19:29:29, 2024-9-2 17:15:28, 2025-8-3 21:28:18, 2025-11-9 16:20:23
4
+ * Last: 2020-3-29 19:37:25, 2022-07-24 22:38:11, 2023-5-24 18:49:18, 2023-6-13 22:20:21, 2023-12-11 13:58:54, 2023-12-14 13:14:40, 2023-12-21 00:04:40, 2024-4-11 19:29:29, 2024-9-2 17:15:28, 2025-8-3 21:28:18, 2025-11-9 16:20:23, 2026-4-30 13:49:44
5
5
  */
6
6
  import * as kebab from '#kebab/index.js';
7
7
  import * as ctr from '#kebab/sys/ctr.js';
@@ -230,6 +230,11 @@ export declare class Sql {
230
230
  * @param str
231
231
  */
232
232
  private _isField;
233
+ /**
234
+ * --- 判断传入值是否是 value 对象(由 value() 函数创建) ---
235
+ * @param arg
236
+ */
237
+ private _isValue;
233
238
  /** --- 获取占位符 --- */
234
239
  private _placeholder;
235
240
  /**
@@ -269,6 +274,15 @@ export declare function column(field: string): {
269
274
  'token': string;
270
275
  'value': string;
271
276
  };
277
+ /**
278
+ * --- 创建字面量值对象,用于 where 条件中 v[0] 需要是值而非字段名的场景 ---
279
+ * --- 例:[value('hello'), 'IN', column('tags')] ---
280
+ */
281
+ export declare function value(val: kebab.DbValue): {
282
+ 'type': 'value';
283
+ 'token': string;
284
+ 'value': kebab.DbValue;
285
+ };
272
286
  /**
273
287
  * --- 将对象转换为 JSON 字符串并避开类型检查,用于适配 PostgreSQL 的 jsonb 字段 ---
274
288
  * @param obj 要转换的 JSON 对象
package/lib/sql.js CHANGED
@@ -26,6 +26,8 @@ export var EJSON;
26
26
  })(EJSON || (EJSON = {}));
27
27
  /** --- field 用 token --- */
28
28
  let columnToken = '';
29
+ /** --- value 用 token --- */
30
+ let valueToken = '';
29
31
  export class Sql {
30
32
  /** --- ctr 对象 --- */
31
33
  _ctr;
@@ -549,13 +551,19 @@ export class Sql {
549
551
  else {
550
552
  // --- 2, 6 ---
551
553
  const nv = v[2];
554
+ // --- v[0] 也可以是 value() 包裹的字面量值,而不一定是字段名 ---
555
+ const isv0 = this._isValue(v[0]);
556
+ const v0sql = isv0 ? this._placeholder() : this.field(v[0]);
557
+ if (isv0) {
558
+ data.push(v[0].value);
559
+ }
552
560
  const isf = this._isField(nv);
553
561
  if (isf) {
554
562
  // --- 6. field ---
555
- sql += this.field(v[0]) + ' ' + v[1] + ' ' + this.field(nv.value) + ' AND ';
563
+ sql += v0sql + ' ' + v[1] + ' ' + this.field(nv.value) + ' AND ';
556
564
  }
557
565
  else {
558
- sql += this.field(v[0]) + ' ' + v[1] + ' ' + this._placeholder() + ' AND ';
566
+ sql += v0sql + ' ' + v[1] + ' ' + this._placeholder() + ' AND ';
559
567
  data.push(nv);
560
568
  }
561
569
  }
@@ -962,6 +970,16 @@ export class Sql {
962
970
  }
963
971
  return true;
964
972
  }
973
+ /**
974
+ * --- 判断传入值是否是 value 对象(由 value() 函数创建) ---
975
+ * @param arg
976
+ */
977
+ _isValue(arg) {
978
+ if (arg === null || typeof arg !== 'object' || arg.type !== 'value' || arg.token !== valueToken || arg.value === undefined) {
979
+ return false;
980
+ }
981
+ return true;
982
+ }
965
983
  /** --- 获取占位符 --- */
966
984
  _placeholder() {
967
985
  return this._service === ESERVICE.MYSQL ? '?' : `$${this._placeholderCounter++}`;
@@ -1172,6 +1190,20 @@ export function column(field) {
1172
1190
  'value': field
1173
1191
  };
1174
1192
  }
1193
+ /**
1194
+ * --- 创建字面量值对象,用于 where 条件中 v[0] 需要是值而非字段名的场景 ---
1195
+ * --- 例:[value('hello'), 'IN', column('tags')] ---
1196
+ */
1197
+ export function value(val) {
1198
+ if (!valueToken) {
1199
+ valueToken = lCore.random(8, lCore.RANDOM_LUNS);
1200
+ }
1201
+ return {
1202
+ 'token': valueToken,
1203
+ 'type': 'value',
1204
+ 'value': val
1205
+ };
1206
+ }
1175
1207
  /**
1176
1208
  * --- 将对象转换为 JSON 字符串并避开类型检查,用于适配 PostgreSQL 的 jsonb 字段 ---
1177
1209
  * @param obj 要转换的 JSON 对象
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "9.2.7",
3
+ "version": "9.3.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
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Project: Kebab, User: JianSuoQiYue
3
3
  * Date: 2019-5-2 21:03:42
4
- * Last: 2020-3-7 10:33:17, 2022-07-22 13:40:10, 2022-09-06 22:40:58, 2024-2-7 01:44:59, 2024-7-2 15:17:09, 2025-6-13 13:06:43, 2025-12-5 13:15:03, 2026-3-22 00:00:00
4
+ * Last: 2020-3-7 10:33:17, 2022-07-22 13:40:10, 2022-09-06 22:40:58, 2024-2-7 01:44:59, 2024-7-2 15:17:09, 2025-6-13 13:06:43, 2025-12-5 13:15:03, 2026-3-22 00:00:00, 2026-4-30 13:49:44
5
5
  */
6
6
  import cluster from 'cluster';
7
7
  import * as os from 'os';
@@ -543,67 +543,101 @@ function startFileWatcher() {
543
543
  /** --- 是否正在重启中 --- */
544
544
  let restarting = false;
545
545
  /**
546
- * --- 监听指定目录的文件变化 ---
547
- * @param dir 要监听的目录路径
546
+ * --- 不需要监听的目录名集合,在 Linux 上 fs.watch recursive 会为每个子目录注册 inotify ---
547
+ * --- 这些目录若被监听会大量消耗系统 file watcher 配额(ENOSPC),必须跳过 ---
548
+ */
549
+ const SKIP_DIRS = new Set(['.git', '.svn', '.hg', 'node_modules', 'log', 'ftmp']);
550
+ /** --- 触发 worker 重载 --- */
551
+ const triggerReload = (formatName) => {
552
+ if (debounceTimer) {
553
+ clearTimeout(debounceTimer);
554
+ }
555
+ // --- 防抖:500ms 内多次变化只触发一次 ---
556
+ debounceTimer = setTimeout(() => {
557
+ debounceTimer = null;
558
+ if (restarting) {
559
+ return;
560
+ }
561
+ restarting = true;
562
+ lCore.display(`[HMR] File changed: ${formatName}, reloading workers...`);
563
+ (async () => {
564
+ // --- 为所有子进程发送 stop 信息并重启 ---
565
+ for (const pid in workerList) {
566
+ workerList[pid].worker.send({
567
+ 'action': 'stop'
568
+ });
569
+ await createChildProcess(workerList[pid].cpu);
570
+ delete workerList[pid];
571
+ }
572
+ restarting = false;
573
+ lCore.display('[HMR] All workers reloaded.');
574
+ })().catch((e) => {
575
+ restarting = false;
576
+ lCore.display('[HMR] Reload error:', e);
577
+ });
578
+ }, 500);
579
+ };
580
+ /**
581
+ * --- 递归收集指定根目录下所有需要监听的子目录(跳过 SKIP_DIRS 和隐藏目录)---
582
+ * @param dir 当前目录路径
583
+ * @param result 收集结果数组
584
+ */
585
+ const collectDirs = async (dir, result) => {
586
+ let entries;
587
+ try {
588
+ entries = await fs.promises.readdir(dir, { 'withFileTypes': true });
589
+ }
590
+ catch {
591
+ return;
592
+ }
593
+ for (const entry of entries) {
594
+ if (!entry.isDirectory()) {
595
+ continue;
596
+ }
597
+ // --- 跳过隐藏目录(以 . 开头)及无需监听的目录 ---
598
+ if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) {
599
+ continue;
600
+ }
601
+ const subDir = `${dir}/${entry.name}`;
602
+ result.push(subDir);
603
+ await collectDirs(subDir, result);
604
+ }
605
+ };
606
+ /**
607
+ * --- 监听指定目录下所有需要监听的目录(非递归逐个注册,避免 OS 级别监听到 .git 等目录)---
608
+ * @param dir 要监听的根目录路径
548
609
  */
549
610
  const watchDir = async (dir) => {
611
+ // --- 去除末尾的路径分隔符,防止拼接时产生双斜杠 ---
612
+ dir = dir.replace(/\/+$/, '');
550
613
  if (!await lFs.isDir(dir)) {
551
614
  return;
552
615
  }
553
- try {
554
- const watcher = fs.watch(dir, { 'recursive': true }, (eventType, filename) => {
555
- if (!filename) {
556
- return;
557
- }
558
- const formatName = filename.replace(/\\/g, '/');
559
- // --- 仅关注 .js 文件和 .json 配置文件的变更 ---
560
- if (!formatName.endsWith('.js') &&
561
- !formatName.endsWith('.json')) {
562
- return;
563
- }
564
- // --- 忽略日志目录、临时目录、git 目录和 node_modules ---
565
- if (formatName.includes('log/') ||
566
- formatName.includes('ftmp/') ||
567
- formatName.includes('node_modules/') ||
568
- formatName.includes('.git/')) {
569
- return;
570
- }
571
- // --- 防抖:500ms 内多次变化只触发一次 ---
572
- if (debounceTimer) {
573
- clearTimeout(debounceTimer);
574
- }
575
- debounceTimer = setTimeout(() => {
576
- debounceTimer = null;
577
- if (restarting) {
616
+ // --- 收集所有需要监听的目录(含根目录本身)---
617
+ const dirs = [dir];
618
+ await collectDirs(dir, dirs);
619
+ for (const d of dirs) {
620
+ try {
621
+ const watcher = fs.watch(d, (eventType, filename) => {
622
+ if (!filename) {
578
623
  return;
579
624
  }
580
- restarting = true;
581
- lCore.display(`[HMR] File changed: ${formatName}, reloading workers...`);
582
- (async () => {
583
- // --- 为所有子进程发送 stop 信息并重启 ---
584
- for (const pid in workerList) {
585
- workerList[pid].worker.send({
586
- 'action': 'stop'
587
- });
588
- await createChildProcess(workerList[pid].cpu);
589
- delete workerList[pid];
590
- }
591
- restarting = false;
592
- lCore.display('[HMR] All workers reloaded.');
593
- })().catch((e) => {
594
- restarting = false;
595
- lCore.display('[HMR] Reload error:', e);
596
- });
597
- }, 500);
598
- });
599
- watcher.on('error', (err) => {
600
- lCore.display(`[HMR] Watcher error on ${dir}:`, err);
601
- });
602
- lCore.display(`[HMR] Watching directory: ${dir}`);
603
- }
604
- catch (err) {
605
- lCore.display(`[HMR] Cannot watch directory: ${dir}`, err);
625
+ const formatName = filename.replace(/\\/g, '/');
626
+ // --- 仅关注 .js 文件和 .json 配置文件的变更 ---
627
+ if (!formatName.endsWith('.js') && !formatName.endsWith('.json')) {
628
+ return;
629
+ }
630
+ triggerReload(formatName);
631
+ });
632
+ watcher.on('error', (err) => {
633
+ lCore.display(`[HMR] Watcher error on ${d}:`, err);
634
+ });
635
+ }
636
+ catch (err) {
637
+ lCore.display(`[HMR] Cannot watch directory: ${d}`, err);
638
+ }
606
639
  }
640
+ lCore.display(`[HMR] Watching directory: ${dir} (${dirs.length} dirs)`);
607
641
  };
608
642
  // --- 监听 www/ 目录(用户项目代码)---
609
643
  watchDir(kebab.WWW_CWD).catch(() => { });
package/sys/mod.d.ts CHANGED
@@ -89,6 +89,12 @@ export default class Mod {
89
89
  'token': string;
90
90
  'value': string;
91
91
  };
92
+ /** --- 创建字面量值对象,用于 where 条件中 v[0] 需要是值而非字段名的场景 --- */
93
+ static value(val: kebab.DbValue): {
94
+ 'type': 'value';
95
+ 'token': string;
96
+ 'value': kebab.DbValue;
97
+ };
92
98
  /** --- 创建 JSON 字符串对象,用于 PGSQL 的 jsonb 字段 --- */
93
99
  static json(obj: kebab.Json): any;
94
100
  /**
package/sys/mod.js CHANGED
@@ -119,6 +119,10 @@ export default class Mod {
119
119
  static column(field) {
120
120
  return lSql.column(field);
121
121
  }
122
+ /** --- 创建字面量值对象,用于 where 条件中 v[0] 需要是值而非字段名的场景 --- */
123
+ static value(val) {
124
+ return lSql.value(val);
125
+ }
122
126
  /** --- 创建 JSON 字符串对象,用于 PGSQL 的 jsonb 字段 --- */
123
127
  static json(obj) {
124
128
  return lSql.json(obj);
@@ -3438,6 +3438,29 @@ Result:<pre id="result">Nothing.</pre>`);
3438
3438
  ]);</pre>
3439
3439
  <b>getSql() :</b> ${s}<br>
3440
3440
  <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
3441
+ <b>format() :</b> ${sql.format(s, sd)}<hr>`);
3442
+ // --- 正则匹配:MySQL 用 REGEXP,PostgreSQL 用 ~ ---
3443
+ // --- value() 对比 column():左侧为字面量值,右侧为字段 ---
3444
+ s = sql.select('*', 'user').where([
3445
+ [lSql.value('info'), this._get['s'] === 'pgsql' ? '~' : 'REGEXP', lSql.column('pattern')]
3446
+ ]).getSql();
3447
+ sd = sql.getData();
3448
+ echo.push(`<pre>sql.select('*', 'user').where([
3449
+ [lSql.value('info'), '${this._get['s'] === 'pgsql' ? '~' : 'REGEXP'}', lSql.column('pattern')]
3450
+ ]);</pre>
3451
+ <b>getSql() :</b> ${s}<br>
3452
+ <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
3453
+ <b>format() :</b> ${sql.format(s, sd)}<hr>`);
3454
+ // --- value() 与普通字段混用 ---
3455
+ s = sql.select('*', 'user').where([
3456
+ [lSql.value(10), '<', lSql.column('age')]
3457
+ ]).getSql();
3458
+ sd = sql.getData();
3459
+ echo.push(`<pre>sql.select('*', 'user').where([
3460
+ [lSql.value(10), '&lt;', lSql.column('age')]
3461
+ ]);</pre>
3462
+ <b>getSql() :</b> ${s}<br>
3463
+ <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
3441
3464
  <b>format() :</b> ${sql.format(s, sd)}`);
3442
3465
  break;
3443
3466
  }