asaihost-nodejs 0.0.1 → 0.0.2

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 (79) hide show
  1. package/README.md +76 -25
  2. package/dist/InterventionLog-CRP6YJVM.cjs +30 -0
  3. package/dist/{InterventionLog-25EHE4QR.cjs.map → InterventionLog-CRP6YJVM.cjs.map} +1 -1
  4. package/dist/{InterventionLog-R6CCPU2B.es.js → InterventionLog-PZT2CCN3.es.js} +5 -5
  5. package/dist/LogStore-7MTUQA4G.cjs +54 -0
  6. package/dist/{LogStore-M2AUBNGO.cjs.map → LogStore-7MTUQA4G.cjs.map} +1 -1
  7. package/dist/{LogStore-ECXX34WC.es.js → LogStore-Q35YFNAY.es.js} +3 -3
  8. package/dist/asaihost-nodejs.cjs +1296 -1410
  9. package/dist/asaihost-nodejs.cjs.map +1 -1
  10. package/dist/asaihost-nodejs.d.cts +154 -17
  11. package/dist/asaihost-nodejs.d.ts +154 -17
  12. package/dist/asaihost-nodejs.es.js +911 -1025
  13. package/dist/asaihost-nodejs.es.js.map +1 -1
  14. package/dist/{chunk-HHPGUJ75.cjs → chunk-2BCIIKRG.cjs} +13 -6
  15. package/dist/chunk-2BCIIKRG.cjs.map +1 -0
  16. package/dist/{chunk-G4WVDUBH.cjs → chunk-4E7Z5K5U.cjs} +7 -7
  17. package/dist/{chunk-G4WVDUBH.cjs.map → chunk-4E7Z5K5U.cjs.map} +1 -1
  18. package/dist/{chunk-DROOHKB3.es.js → chunk-57UQJKSL.es.js} +2 -2
  19. package/dist/{chunk-EOGZPRML.es.js → chunk-A5CDJGJI.es.js} +224 -39
  20. package/dist/chunk-A5CDJGJI.es.js.map +1 -0
  21. package/dist/{chunk-E7V3EBDB.es.js → chunk-A6VJPLDD.es.js} +5 -3
  22. package/dist/chunk-A6VJPLDD.es.js.map +1 -0
  23. package/dist/{chunk-X7VHW2TD.cjs → chunk-H3K33HBA.cjs} +249 -64
  24. package/dist/chunk-H3K33HBA.cjs.map +1 -0
  25. package/dist/{chunk-KOFCBB3N.cjs → chunk-K2QPSQEV.cjs} +9 -9
  26. package/dist/{chunk-KOFCBB3N.cjs.map → chunk-K2QPSQEV.cjs.map} +1 -1
  27. package/dist/{chunk-LOMSUPPU.es.js → chunk-LTEP4XB2.es.js} +6 -6
  28. package/dist/{chunk-QWVR2HNT.cjs → chunk-LXJMGLKW.cjs} +21 -19
  29. package/dist/chunk-LXJMGLKW.cjs.map +1 -0
  30. package/dist/{chunk-4WJDC4M4.es.js → chunk-NLZNIGNF.es.js} +2 -2
  31. package/dist/chunk-NLZNIGNF.es.js.map +1 -0
  32. package/dist/{chunk-W7DVKGX5.cjs → chunk-OCEBEEP5.cjs} +2 -2
  33. package/dist/{chunk-W7DVKGX5.cjs.map → chunk-OCEBEEP5.cjs.map} +1 -1
  34. package/dist/{chunk-NLBFIRDB.cjs → chunk-RIRKIYET.cjs} +12 -12
  35. package/dist/{chunk-NLBFIRDB.cjs.map → chunk-RIRKIYET.cjs.map} +1 -1
  36. package/dist/{chunk-XBIN4C2V.es.js → chunk-SB23ESX2.es.js} +2 -2
  37. package/dist/{chunk-VLMM735Q.cjs → chunk-TKJAHTPO.cjs} +26 -26
  38. package/dist/{chunk-VLMM735Q.cjs.map → chunk-TKJAHTPO.cjs.map} +1 -1
  39. package/dist/{chunk-MCMADJQR.es.js → chunk-VUZO7DCA.es.js} +4 -4
  40. package/dist/chunk-VUZO7DCA.es.js.map +1 -0
  41. package/dist/{chunk-KUZQ36IJ.es.js → chunk-ZQBHJRAO.es.js} +10 -3
  42. package/dist/chunk-ZQBHJRAO.es.js.map +1 -0
  43. package/dist/intervention-hooks-KF3SNB5Q.cjs +21 -0
  44. package/dist/{intervention-hooks-LE6X4C6N.cjs.map → intervention-hooks-KF3SNB5Q.cjs.map} +1 -1
  45. package/dist/{intervention-hooks-FI4EHOL5.es.js → intervention-hooks-TCGIC4IV.es.js} +6 -6
  46. package/dist/log-formats-TUXGMCCU.cjs +31 -0
  47. package/dist/{log-formats-FKFMQKNO.cjs.map → log-formats-TUXGMCCU.cjs.map} +1 -1
  48. package/dist/{log-formats-I5E32H5F.es.js → log-formats-WW3TDUAK.es.js} +2 -2
  49. package/dist/{password-63OY27RX.es.js → password-4BABWAG4.es.js} +4 -4
  50. package/dist/password-PJHJRGPE.cjs +20 -0
  51. package/dist/{password-NZ5EGDWG.cjs.map → password-PJHJRGPE.cjs.map} +1 -1
  52. package/dist/user-store-6J2PSEIN.cjs +12 -0
  53. package/dist/{user-store-SX4N6PQ4.cjs.map → user-store-6J2PSEIN.cjs.map} +1 -1
  54. package/dist/user-store-UC5URX3C.es.js +12 -0
  55. package/package.json +2 -13
  56. package/dist/InterventionLog-25EHE4QR.cjs +0 -30
  57. package/dist/LogStore-M2AUBNGO.cjs +0 -54
  58. package/dist/chunk-4WJDC4M4.es.js.map +0 -1
  59. package/dist/chunk-E7V3EBDB.es.js.map +0 -1
  60. package/dist/chunk-EOGZPRML.es.js.map +0 -1
  61. package/dist/chunk-HHPGUJ75.cjs.map +0 -1
  62. package/dist/chunk-KUZQ36IJ.es.js.map +0 -1
  63. package/dist/chunk-MCMADJQR.es.js.map +0 -1
  64. package/dist/chunk-QWVR2HNT.cjs.map +0 -1
  65. package/dist/chunk-X7VHW2TD.cjs.map +0 -1
  66. package/dist/intervention-hooks-LE6X4C6N.cjs +0 -21
  67. package/dist/log-formats-FKFMQKNO.cjs +0 -31
  68. package/dist/password-NZ5EGDWG.cjs +0 -20
  69. package/dist/user-store-J7LWEXSW.es.js +0 -12
  70. package/dist/user-store-SX4N6PQ4.cjs +0 -12
  71. /package/dist/{InterventionLog-R6CCPU2B.es.js.map → InterventionLog-PZT2CCN3.es.js.map} +0 -0
  72. /package/dist/{LogStore-ECXX34WC.es.js.map → LogStore-Q35YFNAY.es.js.map} +0 -0
  73. /package/dist/{chunk-DROOHKB3.es.js.map → chunk-57UQJKSL.es.js.map} +0 -0
  74. /package/dist/{chunk-LOMSUPPU.es.js.map → chunk-LTEP4XB2.es.js.map} +0 -0
  75. /package/dist/{chunk-XBIN4C2V.es.js.map → chunk-SB23ESX2.es.js.map} +0 -0
  76. /package/dist/{intervention-hooks-FI4EHOL5.es.js.map → intervention-hooks-TCGIC4IV.es.js.map} +0 -0
  77. /package/dist/{log-formats-I5E32H5F.es.js.map → log-formats-WW3TDUAK.es.js.map} +0 -0
  78. /package/dist/{password-63OY27RX.es.js.map → password-4BABWAG4.es.js.map} +0 -0
  79. /package/dist/{user-store-J7LWEXSW.es.js.map → user-store-UC5URX3C.es.js.map} +0 -0
@@ -4,10 +4,10 @@ import {
4
4
  logSecurityConfigIntervention,
5
5
  logSessionTerminate,
6
6
  logUserRbacChange
7
- } from "./chunk-XBIN4C2V.es.js";
7
+ } from "./chunk-SB23ESX2.es.js";
8
8
  import {
9
9
  InterventionLog_exports
10
- } from "./chunk-MCMADJQR.es.js";
10
+ } from "./chunk-VUZO7DCA.es.js";
11
11
  import {
12
12
  LogStore_exports,
13
13
  archiveInterventionYear,
@@ -28,7 +28,7 @@ import {
28
28
  queryUnifiedLogs,
29
29
  readWebLogFromStore,
30
30
  searchUnifiedLogs
31
- } from "./chunk-LOMSUPPU.es.js";
31
+ } from "./chunk-LTEP4XB2.es.js";
32
32
  import {
33
33
  W3C_ACCESS_FIELDS,
34
34
  W3C_SERVER_FIELDS,
@@ -38,7 +38,7 @@ import {
38
38
  formatW3CHeader,
39
39
  formatW3CServerRecord,
40
40
  parseCLF
41
- } from "./chunk-4WJDC4M4.es.js";
41
+ } from "./chunk-NLZNIGNF.es.js";
42
42
  import {
43
43
  getNowTime
44
44
  } from "./chunk-UTKVJE7N.es.js";
@@ -48,7 +48,7 @@ import {
48
48
  secureCompare,
49
49
  validatePasswordChange,
50
50
  verifyPassword
51
- } from "./chunk-DROOHKB3.es.js";
51
+ } from "./chunk-57UQJKSL.es.js";
52
52
  import {
53
53
  USER_LV,
54
54
  applySecretsToHostConfig,
@@ -60,8 +60,9 @@ import {
60
60
  resolveHttpsCertFromPaths,
61
61
  stripForbiddenSecretsFromHostConfig,
62
62
  validateAsaiSecrets
63
- } from "./chunk-E7V3EBDB.es.js";
63
+ } from "./chunk-A6VJPLDD.es.js";
64
64
  import {
65
+ getDefaultResetPassword,
65
66
  getPasswordPolicy,
66
67
  getPermissionThreshold,
67
68
  getPublicSecurityConfig,
@@ -70,11 +71,12 @@ import {
70
71
  isValidUserLv,
71
72
  loadUserLevelConfig,
72
73
  resolveRoleForLevel,
73
- saveUserLevelConfig
74
- } from "./chunk-EOGZPRML.es.js";
74
+ saveUserLevelConfig,
75
+ validatePasswordWithPolicy
76
+ } from "./chunk-A5CDJGJI.es.js";
75
77
  import {
76
78
  getDataServerForAsDb
77
- } from "./chunk-KUZQ36IJ.es.js";
79
+ } from "./chunk-ZQBHJRAO.es.js";
78
80
  import {
79
81
  __export,
80
82
  __name
@@ -210,7 +212,7 @@ ${reason.name}: ${reason.message}`;
210
212
  if (restartTimes.length > MAX_RESTARTS) {
211
213
  try {
212
214
  $asai.$log("NO_RESTART", "Process crashed too frequently, gracefully shutdown and wait for daemon restart.");
213
- void import("./intervention-hooks-FI4EHOL5.es.js").then(
215
+ void import("./intervention-hooks-TCGIC4IV.es.js").then(
214
216
  ({ logProcessGuardEvent }) => logProcessGuardEvent($asai, {
215
217
  action: "crash_loop_shutdown",
216
218
  detail: `${restartTimes.length} crashes in ${RESTART_WINDOW}ms window`,
@@ -252,7 +254,7 @@ ${reason.name}: ${reason.message}`;
252
254
  }
253
255
  __name(initProcess, "initProcess");
254
256
  function gracefulShutdown($asai) {
255
- void import("./intervention-hooks-FI4EHOL5.es.js").then(
257
+ void import("./intervention-hooks-TCGIC4IV.es.js").then(
256
258
  ({ logProcessGuardEvent }) => logProcessGuardEvent($asai, {
257
259
  action: "graceful_shutdown",
258
260
  detail: "SIGTERM/SIGINT or crash guard triggered",
@@ -395,7 +397,7 @@ var LogBuffer = class _LogBuffer {
395
397
  }
396
398
  };
397
399
  function saveLog($asai, src, logstr) {
398
- return new Promise((resolve7, reject) => {
400
+ return new Promise((resolve6, reject) => {
399
401
  try {
400
402
  const bufferKey = `${$asai.id || "default"}_${src}`;
401
403
  let logBuffer = logBuffers.get(bufferKey);
@@ -404,7 +406,7 @@ function saveLog($asai, src, logstr) {
404
406
  logBuffers.set(bufferKey, logBuffer);
405
407
  }
406
408
  logBuffer.add(logstr);
407
- resolve7();
409
+ resolve6();
408
410
  } catch (err) {
409
411
  console.error(err);
410
412
  reject(err);
@@ -424,7 +426,7 @@ async function ensureW3CHeader($asai, filePath, dateCur) {
424
426
  } catch {
425
427
  }
426
428
  const header = formatW3CHeader({
427
- software: $asai.hostconfig.website?.title || "AsaiHost",
429
+ software: $asai.hostconfig.website?.title || "AsaiHostNodejs",
428
430
  version: $asai.hostconfig.website?.ver || "1.0.0.0",
429
431
  fields: [...W3C_SERVER_FIELDS],
430
432
  date: dateCur
@@ -507,7 +509,7 @@ async function ensureW3CAccessHeader($asai, filePath, dateCur) {
507
509
  } catch {
508
510
  }
509
511
  const header = formatW3CHeader({
510
- software: $asai.hostconfig.website?.title || "AsaiHost",
512
+ software: $asai.hostconfig.website?.title || "AsaiHostNodejs",
511
513
  version: $asai.hostconfig.website?.ver || "1.0.0.0",
512
514
  fields: [...W3C_ACCESS_FIELDS],
513
515
  date: dateCur
@@ -843,7 +845,7 @@ function verifyConfigManifest(serverRoot) {
843
845
  __name(verifyConfigManifest, "verifyConfigManifest");
844
846
 
845
847
  // src/core/init.ts
846
- function initAsaiHost($asai, _opt) {
848
+ function initAsaiHostNodejs($asai, _opt) {
847
849
  if (!$asai.hostconfig?.ip) {
848
850
  $asai.hostconfig.ip = getIp();
849
851
  }
@@ -870,7 +872,7 @@ function initAsaiHost($asai, _opt) {
870
872
  type: "CONFIG_WARN",
871
873
  detail: "Using dev-only password; set ASAI_HOST_PASSWORD for production (IEC 62443-4-2 CR 1.7)"
872
874
  });
873
- void import("./intervention-hooks-FI4EHOL5.es.js").then(
875
+ void import("./intervention-hooks-TCGIC4IV.es.js").then(
874
876
  ({ logSecurityConfigIntervention: logSecurityConfigIntervention2 }) => logSecurityConfigIntervention2($asai, {
875
877
  section: "password",
876
878
  detail: "dev-default-password detected at startup"
@@ -890,13 +892,13 @@ function initAsaiHost($asai, _opt) {
890
892
  if (!ntp.ok) logSecurityEvent($asai, { type: "CONFIG_WARN", detail: `NTP: ${ntp.detail}` });
891
893
  });
892
894
  }
893
- __name(initAsaiHost, "initAsaiHost");
895
+ __name(initAsaiHostNodejs, "initAsaiHostNodejs");
894
896
  function scheduleLogComplianceCheck($asai) {
895
897
  const hours = Number($asai?.hostconfig?.logger?.complianceIntervalHours) || 6;
896
898
  if (hours <= 0) return;
897
899
  const ms = hours * 3600 * 1e3;
898
900
  setInterval(() => {
899
- void import("./LogStore-ECXX34WC.es.js").then(
901
+ void import("./LogStore-Q35YFNAY.es.js").then(
900
902
  ({ getComplianceReport: getComplianceReport2 }) => getComplianceReport2($asai, "prod")
901
903
  ).catch(() => void 0);
902
904
  }, ms);
@@ -947,16 +949,61 @@ function getWsUserinfo(req, $asai) {
947
949
  }
948
950
  }
949
951
  __name(getWsUserinfo, "getWsUserinfo");
952
+ function listOnlineWsUsers($asai, hostdir) {
953
+ const users = [];
954
+ const map = $asai.connectionsws || {};
955
+ const dirs = hostdir ? [hostdir] : Object.keys(map);
956
+ for (const dir of dirs) {
957
+ for (const [us, ws2] of Object.entries(map[dir] || {})) {
958
+ if (!us || us === "asai") continue;
959
+ if (isWsStillActive(ws2)) {
960
+ users.push(us);
961
+ } else if (map[dir]?.[us] === ws2) {
962
+ delete map[dir][us];
963
+ }
964
+ }
965
+ }
966
+ return [...new Set(users)];
967
+ }
968
+ __name(listOnlineWsUsers, "listOnlineWsUsers");
950
969
  function isUserOnlineOnWs($asai, us, hostdir) {
951
970
  if (!us || us === "asai") return false;
952
971
  const map = $asai.connectionsws || {};
953
972
  const dirs = hostdir ? [hostdir] : Object.keys(map);
954
973
  for (const dir of dirs) {
955
- if (map[dir]?.[us]) return true;
974
+ const ws2 = map[dir]?.[us];
975
+ if (!ws2) continue;
976
+ if (isWsStillActive(ws2)) return true;
977
+ if (map[dir]?.[us] === ws2) delete map[dir][us];
956
978
  }
957
979
  return false;
958
980
  }
959
981
  __name(isUserOnlineOnWs, "isUserOnlineOnWs");
982
+ function kickUserWs($asai, us, hostdir) {
983
+ if (!us || us === "asai") return;
984
+ const map = $asai.connectionsws || {};
985
+ const dirs = hostdir ? [hostdir] : Object.keys(map);
986
+ for (const dir of dirs) {
987
+ const ws2 = map[dir]?.[us];
988
+ if (!ws2) continue;
989
+ try {
990
+ ws2.close();
991
+ } catch {
992
+ }
993
+ if (map[dir]?.[us] === ws2) delete map[dir][us];
994
+ }
995
+ }
996
+ __name(kickUserWs, "kickUserWs");
997
+ async function removeSessionOnWsClose($asai, us, token) {
998
+ try {
999
+ const store = createUserStore($asai);
1000
+ const sessions = await store.getSessions();
1001
+ const match = sessions.find((s) => s.us === us && s.pub === token);
1002
+ if (match) await store.removeSession(match.id);
1003
+ } catch {
1004
+ }
1005
+ }
1006
+ __name(removeSessionOnWsClose, "removeSessionOnWsClose");
960
1007
  function isWsStillActive(ws2) {
961
1008
  if (!ws2) return false;
962
1009
  const state = ws2.readyState;
@@ -1039,8 +1086,11 @@ function isOkWs($asai, hostdir, ws2, _req, hostcfg) {
1039
1086
  __name(isOkWs, "isOkWs");
1040
1087
  function loginWs($asai, hostdir, ws2, req, hostcfg, wsWorkHandler, logWsMessage) {
1041
1088
  const [us] = ws2.Userinfo || [];
1042
- if (hostcfg.ckws && us) {
1043
- if (!ws2.tasksws) ws2.tasksws = {};
1089
+ if (!$asai.connectionsws[hostdir]) {
1090
+ $asai.connectionsws[hostdir] = {};
1091
+ }
1092
+ if (us && us !== "asai") {
1093
+ if (hostcfg.ckws && !ws2.tasksws) ws2.tasksws = {};
1044
1094
  $asai.connectionsws[hostdir][us] = ws2;
1045
1095
  }
1046
1096
  ws2.on("message", (msg) => {
@@ -1048,12 +1098,14 @@ function loginWs($asai, hostdir, ws2, req, hostcfg, wsWorkHandler, logWsMessage)
1048
1098
  wsWorkHandler?.(msg, ws2, hostdir, req);
1049
1099
  });
1050
1100
  ws2.on("close", () => {
1051
- if (hostcfg.ckws) {
1052
- if (ws2.tasksws) {
1053
- Object.values(ws2.tasksws).forEach((el) => el && clearInterval(el));
1054
- ws2.tasksws = void 0;
1055
- }
1056
- if (us && $asai.connectionsws[hostdir]?.[us] === ws2) delete $asai.connectionsws[hostdir][us];
1101
+ const token = ws2.Userinfo?.[2];
1102
+ if (hostcfg.ckws && ws2.tasksws) {
1103
+ Object.values(ws2.tasksws).forEach((el) => el && clearInterval(el));
1104
+ ws2.tasksws = void 0;
1105
+ }
1106
+ if (us && $asai.connectionsws[hostdir]?.[us] === ws2) delete $asai.connectionsws[hostdir][us];
1107
+ if (us && token && !isUserOnlineOnWs($asai, us, hostdir)) {
1108
+ void removeSessionOnWsClose($asai, us, String(token));
1057
1109
  }
1058
1110
  if (ws2.readyState !== WebSocket.CLOSED && ws2.readyState !== WebSocket.CLOSING) ws2.close();
1059
1111
  });
@@ -1406,11 +1458,11 @@ function createSkipChecker($asai) {
1406
1458
  }
1407
1459
  if (exact.size === 0 && prefixes.length === 0) return () => false;
1408
1460
  return (url) => {
1409
- const path8 = normalizeRequestPath(url);
1410
- if (!path8) return false;
1411
- if (exact.has(path8)) return true;
1461
+ const path7 = normalizeRequestPath(url);
1462
+ if (!path7) return false;
1463
+ if (exact.has(path7)) return true;
1412
1464
  for (let i = 0; i < prefixes.length; i++) {
1413
- if (path8.startsWith(prefixes[i])) return true;
1465
+ if (path7.startsWith(prefixes[i])) return true;
1414
1466
  }
1415
1467
  return false;
1416
1468
  };
@@ -1855,7 +1907,7 @@ function releasePort(port, opts) {
1855
1907
  const occupants = findListeningPids(port).filter((p) => p !== exclude && p !== OWN_PID);
1856
1908
  if (!occupants.length) return true;
1857
1909
  const label = opts?.label ? `[${opts.label}] ` : "";
1858
- console.warn(`[AsaiHost] ${label}\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF0C\u6B63\u5728\u7ED3\u675F\u8FDB\u7A0B: ${occupants.join(", ")}`);
1910
+ console.warn(`[AsaiHostNodejs] ${label}\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF0C\u6B63\u5728\u7ED3\u675F\u8FDB\u7A0B: ${occupants.join(", ")}`);
1859
1911
  for (const pid of occupants) {
1860
1912
  killPid(pid);
1861
1913
  }
@@ -2066,18 +2118,18 @@ function asaiGuard($asai, failedHostdir = "asai") {
2066
2118
  const sameProcessOwner = findSameProcessPortOwner($asai, port, failedHostdir);
2067
2119
  if (sameProcessOwner) {
2068
2120
  console.error(
2069
- `[AsaiHost] \u7AEF\u53E3 ${port} \u5DF2\u88AB\u540C\u8FDB\u7A0B\u7AD9\u70B9 [${sameProcessOwner}] \u5360\u7528\uFF0C[${failedHostdir}] \u505C\u6B62\u91CD\u8BD5\uFF08\u8BF7\u68C0\u67E5 asaihost.json \u662F\u5426\u91CD\u590D\u914D\u7F6E port\uFF09`
2121
+ `[AsaiHostNodejs] \u7AEF\u53E3 ${port} \u5DF2\u88AB\u540C\u8FDB\u7A0B\u7AD9\u70B9 [${sameProcessOwner}] \u5360\u7528\uFF0C[${failedHostdir}] \u505C\u6B62\u91CD\u8BD5\uFF08\u8BF7\u68C0\u67E5 asaihost.json \u662F\u5426\u91CD\u590D\u914D\u7F6E port\uFF09`
2070
2122
  );
2071
2123
  return;
2072
2124
  }
2073
2125
  const released = releasePort(port, { excludePid: process.pid, label: failedHostdir });
2074
2126
  $asai.hostconfig.asaiport = conflictPorts.filter((p) => p !== port);
2075
2127
  if (released) {
2076
- console.warn(`[AsaiHost] \u7AEF\u53E3 ${port} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${failedHostdir}]`);
2128
+ console.warn(`[AsaiHostNodejs] \u7AEF\u53E3 ${port} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${failedHostdir}]`);
2077
2129
  scheduleHttpRetry($asai, failedHostdir);
2078
2130
  return;
2079
2131
  }
2080
- console.error(`[AsaiHost] \u7AEF\u53E3 ${port} \u65E0\u6CD5\u91CA\u653E\uFF0C\u542F\u52A8\u5931\u8D25\uFF08netstat -ano | findstr :${port}\uFF09`);
2132
+ console.error(`[AsaiHostNodejs] \u7AEF\u53E3 ${port} \u65E0\u6CD5\u91CA\u653E\uFF0C\u542F\u52A8\u5931\u8D25\uFF08netstat -ano | findstr :${port}\uFF09`);
2081
2133
  process.exit(1);
2082
2134
  }
2083
2135
  __name(asaiGuard, "asaiGuard");
@@ -2184,7 +2236,7 @@ function startProxyServer($asai, proxyhostdir) {
2184
2236
  label: proxyhostdir
2185
2237
  });
2186
2238
  if (released) {
2187
- console.warn(`[AsaiHost] \u4EE3\u7406\u7AEF\u53E3 ${proxycfg.proxyport} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${proxyhostdir}]`);
2239
+ console.warn(`[AsaiHostNodejs] \u4EE3\u7406\u7AEF\u53E3 ${proxycfg.proxyport} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${proxyhostdir}]`);
2188
2240
  setTimeout(() => startProxyServer($asai, proxyhostdir), $asai.hostconfig.tm?.d || 2e3);
2189
2241
  }
2190
2242
  } else {
@@ -2223,8 +2275,8 @@ function src_default($asai, opt) {
2223
2275
  workHandlers.set("apiWork", opt.apiWork);
2224
2276
  workHandlers.set("wsWork", opt.wsWork);
2225
2277
  workHandlers.set("jsonpWork", opt.jsonpWork);
2226
- function StartAsaiHost() {
2227
- initAsaiHost($asai, opt);
2278
+ function StartAsaiHostNodejs() {
2279
+ initAsaiHostNodejs($asai, opt);
2228
2280
  console.log(`\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B`);
2229
2281
  releaseConfiguredPorts($asai);
2230
2282
  if ($asai.hostconfig?.hosts) {
@@ -2246,8 +2298,8 @@ function src_default($asai, opt) {
2246
2298
  });
2247
2299
  }
2248
2300
  }
2249
- __name(StartAsaiHost, "StartAsaiHost");
2250
- return { StartAsaiHost, asaiWs: ws };
2301
+ __name(StartAsaiHostNodejs, "StartAsaiHostNodejs");
2302
+ return { StartAsaiHostNodejs, asaiWs: ws };
2251
2303
  }
2252
2304
  __name(src_default, "default");
2253
2305
 
@@ -2319,7 +2371,7 @@ async function attachServerUserLevel(req, $asai) {
2319
2371
  if (!Array.isArray(userinfo) || !userinfo[0]) return;
2320
2372
  const us = String(userinfo[0]);
2321
2373
  try {
2322
- const { createUserStore: createUserStore2 } = await import("./user-store-J7LWEXSW.es.js");
2374
+ const { createUserStore: createUserStore2 } = await import("./user-store-UC5URX3C.es.js");
2323
2375
  const user = await createUserStore2($asai).findByUsername(us);
2324
2376
  if (user && user.status === "active") {
2325
2377
  req._serverUser = { us: user.us, lv: user.lv, status: user.status };
@@ -2350,11 +2402,11 @@ function getSecurityConfig($asai) {
2350
2402
  return $asai?.hostconfig?.security || {};
2351
2403
  }
2352
2404
  __name(getSecurityConfig, "getSecurityConfig");
2353
- function pathMatchesPattern(path8, pattern) {
2405
+ function pathMatchesPattern(path7, pattern) {
2354
2406
  if (pattern.endsWith("*")) {
2355
- return path8.startsWith(pattern.slice(0, -1));
2407
+ return path7.startsWith(pattern.slice(0, -1));
2356
2408
  }
2357
- return path8 === pattern;
2409
+ return path7 === pattern;
2358
2410
  }
2359
2411
  __name(pathMatchesPattern, "pathMatchesPattern");
2360
2412
  function isAuthSkipped($asai, pathname) {
@@ -2502,6 +2554,54 @@ function isApiCorsAllowed(origin, hostcfg, hostHeader) {
2502
2554
  __name(isApiCorsAllowed, "isApiCorsAllowed");
2503
2555
 
2504
2556
  // src/common/server/Api.ts
2557
+ function readRequestBody(req, maxBody) {
2558
+ return new Promise((resolve6, reject) => {
2559
+ const chunks = [];
2560
+ let received = 0;
2561
+ let settled = false;
2562
+ const finish = /* @__PURE__ */ __name((err, data) => {
2563
+ if (settled) return;
2564
+ settled = true;
2565
+ if (err) reject(err);
2566
+ else resolve6(data ?? "");
2567
+ }, "finish");
2568
+ req.on("data", (chunk) => {
2569
+ if (settled) return;
2570
+ received += chunk.length;
2571
+ if (received > maxBody) {
2572
+ req.destroy();
2573
+ finish(new Error("PAYLOAD_TOO_LARGE"));
2574
+ return;
2575
+ }
2576
+ chunks.push(chunk);
2577
+ });
2578
+ req.on("end", () => finish(null, Buffer.concat(chunks).toString("utf8")));
2579
+ req.on("error", (err) => finish(err));
2580
+ });
2581
+ }
2582
+ __name(readRequestBody, "readRequestBody");
2583
+ function processPostPayload($asai, req, postdata, pm) {
2584
+ let isAes = false;
2585
+ let out = postdata;
2586
+ if ($asai.$lib.aesfns && out?.startsWith?.('{"aes":')) {
2587
+ isAes = true;
2588
+ const aesData = JSON.parse(out);
2589
+ out = $asai.$lib.aesfns.reqAES({ data: aesData });
2590
+ if (aesData?.aes === 2 && typeof out === "string") {
2591
+ try {
2592
+ out = JSON.parse(out);
2593
+ } catch {
2594
+ }
2595
+ }
2596
+ }
2597
+ const plainJson = typeof out === "string" && out.trimStart().startsWith("{");
2598
+ if (req?.Userinfo?.[2] && !plainJson) {
2599
+ pm = req.Userinfo[2];
2600
+ out = $asai.$lib.AsCode.asdecode(out, pm);
2601
+ }
2602
+ return { postdata: out, pm, isAes };
2603
+ }
2604
+ __name(processPostPayload, "processPostPayload");
2505
2605
  var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
2506
2606
  opt?.fn?.($asai);
2507
2607
  function getHeader(req, key) {
@@ -2581,8 +2681,24 @@ var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
2581
2681
  const releaseSlot = /* @__PURE__ */ __name(() => releaseApiSlot(hostdir, clientIp), "releaseSlot");
2582
2682
  res.once("finish", releaseSlot);
2583
2683
  res.once("close", releaseSlot);
2684
+ const maxBody = $asai.hostconfig?.maxbody || 100 * 1024 * 1024;
2685
+ const isTextPost = req.method === "POST" && req.url.indexOf("binary") === -1;
2686
+ const bodyPromise = isTextPost ? readRequestBody(req, maxBody) : null;
2584
2687
  void (async () => {
2585
- const authResult = await validateHttpAuthAsync(req, $asai, pathname);
2688
+ let authResult;
2689
+ let rawBody = "";
2690
+ try {
2691
+ [authResult, rawBody] = await Promise.all([
2692
+ validateHttpAuthAsync(req, $asai, pathname),
2693
+ bodyPromise ?? Promise.resolve("")
2694
+ ]);
2695
+ } catch (err) {
2696
+ releaseSlot();
2697
+ if (err?.message === "PAYLOAD_TOO_LARGE") {
2698
+ return sendError(res, 413, "Payload Too Large");
2699
+ }
2700
+ return sendError(res, 400, err?.message || "Bad Request");
2701
+ }
2586
2702
  if (!authResult.ok) {
2587
2703
  releaseSlot();
2588
2704
  return sendAuthError(res, authResult);
@@ -2596,52 +2712,25 @@ var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
2596
2712
  let qrdata;
2597
2713
  let postdata;
2598
2714
  if (req.url.indexOf("binary") === -1) {
2599
- postdata = "";
2600
2715
  qrdata = query;
2601
2716
  if (req.method === "POST") {
2602
- const chunks = [];
2603
- const maxBody = $asai.hostconfig?.maxbody || 100 * 1024 * 1024;
2604
- let received = 0;
2605
- let aborted = false;
2606
- req.on("data", (chunk) => {
2607
- if (aborted) return;
2608
- received += chunk.length;
2609
- if (received > maxBody) {
2610
- aborted = true;
2611
- sendError(res, 413, "Payload Too Large");
2612
- req.destroy();
2613
- return;
2614
- }
2615
- chunks.push(chunk);
2616
- });
2617
- req.on("end", () => {
2618
- if (aborted) return;
2619
- postdata = Buffer.concat(chunks).toString();
2620
- isAes = $asai.$lib.aesfns && postdata?.startsWith?.('{"aes":');
2621
- if (isAes) {
2622
- const aesData = JSON.parse(postdata);
2623
- postdata = $asai.$lib.aesfns.reqAES({ data: aesData });
2624
- if (aesData?.aes === 2) {
2625
- try {
2626
- postdata = JSON.parse(postdata);
2627
- } catch (err) {
2628
- }
2629
- }
2630
- }
2631
- if (req?.Userinfo && req?.Userinfo[2]) {
2632
- pm = req.Userinfo[2];
2633
- postdata = $asai.$lib.AsCode.asdecode(postdata, pm);
2634
- }
2635
- if (apiFn?.post && typeof apiFn?.post === "function") {
2636
- const optTmp = { qrdata, data: postdata, pm };
2637
- if (isAes || $asai.$lib.aesfns?.allaes) {
2638
- optTmp.resAES = $asai.$lib.aesfns?.resAES;
2639
- }
2640
- apiFn.post(req, res, hostdir, optTmp);
2641
- } else {
2642
- res.end(postdata);
2717
+ try {
2718
+ const processed = processPostPayload($asai, req, rawBody, pm);
2719
+ postdata = processed.postdata;
2720
+ pm = processed.pm;
2721
+ isAes = processed.isAes;
2722
+ } catch {
2723
+ return sendError(res, 400, "AES decrypt failed");
2724
+ }
2725
+ if (apiFn?.post && typeof apiFn?.post === "function") {
2726
+ const optTmp = { qrdata, data: postdata, pm };
2727
+ if (isAes || $asai.$lib.aesfns?.allaes) {
2728
+ optTmp.resAES = $asai.$lib.aesfns?.resAES;
2643
2729
  }
2644
- });
2730
+ apiFn.post(req, res, hostdir, optTmp);
2731
+ } else {
2732
+ res.end(typeof postdata === "string" ? postdata : JSON.stringify(postdata ?? ""));
2733
+ }
2645
2734
  } else if (req.method === "GET") {
2646
2735
  if (apiFn?.get && typeof apiFn?.get === "function") {
2647
2736
  const optTmp = { qrdata, data: qrdata, pm };
@@ -2845,7 +2934,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2845
2934
  }
2846
2935
  __name(getKeyByData, "getKeyByData");
2847
2936
  function clientwsInit() {
2848
- return new Promise((resolve7, reject) => {
2937
+ return new Promise((resolve6, reject) => {
2849
2938
  $asai.clientws[wsname].socket = new $asai.asaiWs(
2850
2939
  $asai.command.commandconfig.clientws[wsname]
2851
2940
  );
@@ -2875,7 +2964,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2875
2964
  `${wsname}=${$asai.command.commandconfig.clientws[wsname]} is open.`
2876
2965
  );
2877
2966
  $asai.clientws[wsname].open = 1;
2878
- resolve7();
2967
+ resolve6();
2879
2968
  };
2880
2969
  });
2881
2970
  }
@@ -2905,7 +2994,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2905
2994
  }
2906
2995
  __name(clientwsOnmessage, "clientwsOnmessage");
2907
2996
  function clientwsApi(messageData) {
2908
- return new Promise((resolve7, reject) => {
2997
+ return new Promise((resolve6, reject) => {
2909
2998
  if ($asai.clientws[wsname].open) {
2910
2999
  try {
2911
3000
  const taskOpt = {};
@@ -2917,7 +3006,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2917
3006
  getKeyByData(messageData),
2918
3007
  {
2919
3008
  callback: /* @__PURE__ */ __name((res) => {
2920
- resolve7(res);
3009
+ resolve6(res);
2921
3010
  }, "callback"),
2922
3011
  ...messageData,
2923
3012
  ...taskOpt
@@ -3434,7 +3523,7 @@ var unifiedlog_default = /* @__PURE__ */ __name(($asai) => {
3434
3523
  if (url.startsWith("/api/asailog/store/formats/")) {
3435
3524
  if (!await ensureLogReadAccess(req, $asai, getPermissionThreshold($asai, "logRead"))) return sendFail(res, "Forbidden", opt);
3436
3525
  try {
3437
- const { getLogFormatTaxonomy } = await import("./log-formats-I5E32H5F.es.js");
3526
+ const { getLogFormatTaxonomy } = await import("./log-formats-WW3TDUAK.es.js");
3438
3527
  return sendSuccess(res, getLogFormatTaxonomy($asai), opt);
3439
3528
  } catch (err) {
3440
3529
  return sendFail(res, err?.message || err, opt);
@@ -3798,7 +3887,7 @@ __name(buildSessionRecord, "buildSessionRecord");
3798
3887
  var API_ADMIN_PREFIXES = ["/api/user/login/admin/"];
3799
3888
  function getMinLevelForApiPath(pathname, $asai) {
3800
3889
  if (API_ADMIN_PREFIXES.some((p) => pathname.startsWith(p))) {
3801
- if (pathname.includes("/admin/update") || pathname.includes("/admin/kick") || pathname.includes("/admin/delete")) {
3890
+ if (pathname.includes("/admin/update") || pathname.includes("/admin/kick") || pathname.includes("/admin/delete") || pathname.includes("/admin/resetpass")) {
3802
3891
  return $asai ? getPermissionThreshold($asai, "adminWrite") : USER_LV.DEVELOPER;
3803
3892
  }
3804
3893
  return $asai ? getPermissionThreshold($asai, "adminRead") : USER_LV.ADMIN;
@@ -3811,19 +3900,98 @@ function hasLevel(userLevel, required) {
3811
3900
  }
3812
3901
  __name(hasLevel, "hasLevel");
3813
3902
 
3814
- // src/sysserver/api/login/index.ts
3815
- function parseBody2(opt) {
3816
- if (!opt?.data) return {};
3817
- if (typeof opt.data === "string") {
3903
+ // src/sysserver/api/login/service/request-body.ts
3904
+ function isAesEnvelope(body) {
3905
+ return typeof body.data === "string" && typeof body.iv === "string" && typeof body.auth === "string" && body.aes != null && body.us == null && body.pass == null && body.password == null;
3906
+ }
3907
+ __name(isAesEnvelope, "isAesEnvelope");
3908
+ function unwrapJsonString(value) {
3909
+ if (typeof value !== "string") return value;
3910
+ const text = value.trim();
3911
+ if (!text.startsWith("{") && !text.startsWith("[")) return value;
3912
+ try {
3913
+ return JSON.parse(text);
3914
+ } catch {
3915
+ return value;
3916
+ }
3917
+ }
3918
+ __name(unwrapJsonString, "unwrapJsonString");
3919
+ function parseAuthBody(opt) {
3920
+ let raw = opt?.data;
3921
+ if (raw == null || raw === "") return {};
3922
+ if (typeof raw === "string") {
3923
+ const text = raw.trim();
3924
+ if (!text) return {};
3818
3925
  try {
3819
- return JSON.parse(opt.data);
3926
+ raw = JSON.parse(text);
3820
3927
  } catch {
3821
- return {};
3928
+ if (text.includes("=") && !text.startsWith("{") && !text.startsWith("[")) {
3929
+ raw = Object.fromEntries(new URLSearchParams(text));
3930
+ } else {
3931
+ return {};
3932
+ }
3822
3933
  }
3934
+ raw = unwrapJsonString(raw);
3823
3935
  }
3824
- return typeof opt.data === "object" ? opt.data : {};
3936
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
3937
+ const body = raw;
3938
+ if (isAesEnvelope(body)) return {};
3939
+ return body;
3940
+ }
3941
+ __name(parseAuthBody, "parseAuthBody");
3942
+ function pickAuthField(body, ...keys) {
3943
+ for (const key of keys) {
3944
+ const val = body[key];
3945
+ if (val != null && String(val).length > 0) return String(val);
3946
+ }
3947
+ return "";
3825
3948
  }
3826
- __name(parseBody2, "parseBody");
3949
+ __name(pickAuthField, "pickAuthField");
3950
+
3951
+ // src/sysserver/api/login/service/online-sessions.ts
3952
+ function resolveOnlineSessions($asai, hostdir, allSessions) {
3953
+ const onlineUsers = new Set(listOnlineWsUsers($asai, hostdir));
3954
+ if (!onlineUsers.size) return [];
3955
+ const byUser = /* @__PURE__ */ new Map();
3956
+ for (const s of allSessions) {
3957
+ if (!onlineUsers.has(s.us)) continue;
3958
+ const prev = byUser.get(s.us);
3959
+ const sTime = s.lastActivityAt ?? s.createdAt;
3960
+ const prevTime = prev ? prev.lastActivityAt ?? prev.createdAt : 0;
3961
+ if (!prev || sTime > prevTime) byUser.set(s.us, s);
3962
+ }
3963
+ return [...byUser.values()].sort((a, b) => b.createdAt - a.createdAt);
3964
+ }
3965
+ __name(resolveOnlineSessions, "resolveOnlineSessions");
3966
+ function isSessionUserOnline($asai, us, hostdir) {
3967
+ if (!us) return false;
3968
+ return isUserOnlineOnWs($asai, us, hostdir);
3969
+ }
3970
+ __name(isSessionUserOnline, "isSessionUserOnline");
3971
+ async function countOnlineSessions($asai, hostdir, store) {
3972
+ const all = await store.getSessions();
3973
+ return resolveOnlineSessions($asai, hostdir, all).length;
3974
+ }
3975
+ __name(countOnlineSessions, "countOnlineSessions");
3976
+ async function countOnlineSessionsByUser($asai, hostdir, store, us) {
3977
+ const all = await store.getSessions();
3978
+ return resolveOnlineSessions($asai, hostdir, all).filter((s) => s.us === us).length;
3979
+ }
3980
+ __name(countOnlineSessionsByUser, "countOnlineSessionsByUser");
3981
+ async function purgeOfflineSessions($asai, hostdir, store) {
3982
+ const onlineUsers = new Set(listOnlineWsUsers($asai, hostdir));
3983
+ const all = await store.getSessions();
3984
+ let purged = 0;
3985
+ for (const s of all) {
3986
+ if (onlineUsers.has(s.us)) continue;
3987
+ await store.removeSession(s.id);
3988
+ purged += 1;
3989
+ }
3990
+ return purged;
3991
+ }
3992
+ __name(purgeOfflineSessions, "purgeOfflineSessions");
3993
+
3994
+ // src/sysserver/api/login/index.ts
3827
3995
  function getSecurity($asai) {
3828
3996
  return $asai?.hostconfig?.security || {};
3829
3997
  }
@@ -3833,16 +4001,16 @@ function getQuota2($asai) {
3833
4001
  return sec.quota || {};
3834
4002
  }
3835
4003
  __name(getQuota2, "getQuota");
3836
- async function canLogin($asai, us) {
4004
+ async function canLogin($asai, us, hostdir) {
3837
4005
  const quota = getQuota2($asai);
3838
4006
  if (!quota.enabled) return { ok: true };
3839
4007
  const store = createUserStore($asai);
3840
- const total = await store.countSessions();
4008
+ const total = await countOnlineSessions($asai, hostdir, store);
3841
4009
  const maxLogin = quota.maxLoginSessions ?? $asai?.hostconfig?.hosts?.default?.maxonline ?? 100;
3842
4010
  if (total >= maxLogin) {
3843
4011
  return { ok: false, message: "LOGIN_QUOTA_FULL" };
3844
4012
  }
3845
- const perUser = await store.countSessionsByUser(us);
4013
+ const perUser = await countOnlineSessionsByUser($asai, hostdir, store, us);
3846
4014
  const maxPerUser = quota.maxSessionsPerUser ?? 3;
3847
4015
  if (perUser >= maxPerUser) {
3848
4016
  return { ok: false, message: "USER_SESSION_LIMIT" };
@@ -3882,13 +4050,13 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3882
4050
  async function post(req, res, hostdir, opt) {
3883
4051
  const url = req.url || "";
3884
4052
  const ip = getClientIp(req);
3885
- const body = parseBody2(opt);
4053
+ const body = parseAuthBody(opt);
3886
4054
  if (url.startsWith("/api/user/login/changepass/")) {
3887
4055
  await ensureReady($asai);
3888
4056
  try {
3889
- const us = sanitizeString(body.us, 64);
3890
- const pass = sanitizeString(body.pass, 128);
3891
- const newPass = sanitizeString(body.newPass, 128);
4057
+ const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
4058
+ const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
4059
+ const newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
3892
4060
  if (!us || !pass || !newPass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
3893
4061
  const user = await store.findByUsername(us);
3894
4062
  if (!user || !verifyPassword(pass, user.passHash, user.passSalt)) {
@@ -3922,9 +4090,11 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3922
4090
  return sendErr(res, "AUTH_REG_DISABLED", void 0, opt);
3923
4091
  }
3924
4092
  try {
3925
- const us = sanitizeString(body.us, 64);
3926
- const pass = sanitizeString(body.pass, 128);
3927
- const email = body.email ? sanitizeString(body.email, 128) : void 0;
4093
+ const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
4094
+ const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
4095
+ const emailRaw = pickAuthField(body, "email");
4096
+ const email = emailRaw ? sanitizeString(emailRaw, 128) : void 0;
4097
+ if (!us || !pass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
3928
4098
  const policyErr = validatePasswordChange(pass, $asai);
3929
4099
  if (policyErr === "PASSWORD_TOO_SHORT") {
3930
4100
  return sendErr(res, policyErr, [String(getPasswordPolicy($asai).minLength)], opt);
@@ -3953,8 +4123,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3953
4123
  if (url.startsWith("/api/user/login/auth/")) {
3954
4124
  await ensureReady($asai);
3955
4125
  try {
3956
- const us = sanitizeString(body.us, 64);
3957
- const pass = sanitizeString(body.pass, 128);
4126
+ const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
4127
+ const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
3958
4128
  if (!us || !pass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
3959
4129
  const user = await store.findByUsername(us);
3960
4130
  if (!user || !verifyPassword(pass, user.passHash, user.passSalt)) {
@@ -3983,17 +4153,9 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3983
4153
  const mustChange = !!user.mustChangePassword || isPasswordExpired(user.passwordChangedAt, policy.maxAgeDays);
3984
4154
  const hostcfg = $asai.getHost?.(hostdir) || {};
3985
4155
  if (hostcfg.ckws && isUserOnlineOnWs($asai, us, hostdir)) {
3986
- logSecurityEvent($asai, {
3987
- type: "AUTH_FAIL",
3988
- path: url,
3989
- method: "POST",
3990
- user: us,
3991
- ip,
3992
- detail: "user_already_online"
3993
- });
3994
- return sendErr(res, "USER_ALREADY_LOGGED", void 0, opt);
4156
+ kickUserWs($asai, us, hostdir);
3995
4157
  }
3996
- const quotaCheck = await canLogin($asai, us);
4158
+ const quotaCheck = await canLogin($asai, us, hostdir);
3997
4159
  if (!quotaCheck.ok) {
3998
4160
  logSecurityEvent($asai, { type: "RATE_LIMIT", path: url, method: "POST", user: us, ip, detail: quotaCheck.message });
3999
4161
  return sendErr(res, quotaCheck.message || "LOGIN_QUOTA_FULL", void 0, opt);
@@ -4037,12 +4199,17 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4037
4199
  if (!sessionId) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
4038
4200
  const sessions = await store.getSessions();
4039
4201
  const target = sessions.find((s) => s.id === sessionId);
4202
+ if (!target) return sendErr(res, "AUTH_SESSION_NOT_FOUND", void 0, opt);
4203
+ if (!isSessionUserOnline($asai, target.us, hostdir)) {
4204
+ return sendErr(res, "SESSION_NOT_ONLINE", void 0, opt);
4205
+ }
4206
+ kickUserWs($asai, target.us, hostdir);
4040
4207
  await store.removeSession(sessionId);
4041
4208
  const operator = String(req?.Userinfo?.[0] || "");
4042
4209
  await logSessionTerminate($asai, {
4043
4210
  operator,
4044
4211
  sessionId,
4045
- targetUs: target?.us,
4212
+ targetUs: target.us,
4046
4213
  ip
4047
4214
  });
4048
4215
  return sendSuccess(res, { ok: true }, opt);
@@ -4074,10 +4241,16 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4074
4241
  const levels = Array.isArray(body.levels) ? body.levels : void 0;
4075
4242
  const thresholds = body.thresholds && typeof body.thresholds === "object" ? body.thresholds : void 0;
4076
4243
  const routeRights = Array.isArray(body.routeRights) ? body.routeRights : void 0;
4244
+ const defaultResetPassword = typeof body.defaultResetPassword === "string" ? body.defaultResetPassword : void 0;
4077
4245
  const operator = String(req?.Userinfo?.[0] || "");
4078
4246
  const cfg = await saveUserLevelConfig(
4079
4247
  $asai,
4080
- { levels, thresholds, routeRights },
4248
+ {
4249
+ levels,
4250
+ thresholds,
4251
+ routeRights,
4252
+ defaultResetPassword
4253
+ },
4081
4254
  operator
4082
4255
  );
4083
4256
  await logSecurityConfigIntervention($asai, {
@@ -4091,6 +4264,57 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4091
4264
  return sendFail(res, err?.message || err, opt);
4092
4265
  }
4093
4266
  }
4267
+ if (url.startsWith("/api/user/login/admin/resetpass/")) {
4268
+ await ensureReady($asai);
4269
+ if (!await checkAdmin(req, url, getPermissionThreshold($asai, "adminWrite"))) {
4270
+ logSecurityEvent($asai, { type: "ACCESS_DENIED", path: url, method: "POST", ip });
4271
+ return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
4272
+ }
4273
+ try {
4274
+ const targetUs = sanitizeString(body.us, 64);
4275
+ if (!targetUs) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4276
+ let newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
4277
+ if (!newPass) newPass = getDefaultResetPassword($asai);
4278
+ const policy = getPasswordPolicy($asai);
4279
+ const policyErr = validatePasswordWithPolicy(newPass, policy);
4280
+ if (policyErr === "PASSWORD_TOO_SHORT") {
4281
+ return sendErr(res, policyErr, [String(policy.minLength)], opt);
4282
+ }
4283
+ if (policyErr) return sendErr(res, policyErr, void 0, opt);
4284
+ const user = await store.findByUsername(targetUs);
4285
+ if (!user) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4286
+ const { passHash, passSalt } = hashPassword(newPass);
4287
+ const updated = await store.updateUser(targetUs, {
4288
+ passHash,
4289
+ passSalt,
4290
+ mustChangePassword: 1,
4291
+ passwordChangedAt: Date.now(),
4292
+ failCount: 0,
4293
+ status: "active",
4294
+ lockedUntil: void 0,
4295
+ passHistory: "[]"
4296
+ });
4297
+ if (!updated) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4298
+ const operator = String(req?.Userinfo?.[0] || "");
4299
+ await logUserRbacChange($asai, {
4300
+ operator,
4301
+ targetUs,
4302
+ patch: { resetPassword: true, mustChangePassword: 1 },
4303
+ ip
4304
+ });
4305
+ logSecurityEvent($asai, {
4306
+ type: "AUTH_FAIL",
4307
+ path: url,
4308
+ method: "POST",
4309
+ user: targetUs,
4310
+ ip,
4311
+ detail: "admin_password_reset"
4312
+ });
4313
+ return sendSuccess(res, { user: publicUser($asai, updated) }, opt);
4314
+ } catch (err) {
4315
+ return sendFail(res, err?.message || err, opt);
4316
+ }
4317
+ }
4094
4318
  if (url.startsWith("/api/user/login/admin/update/")) {
4095
4319
  await ensureReady($asai);
4096
4320
  if (!await checkAdmin(req, url)) {
@@ -4136,11 +4360,12 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4136
4360
  try {
4137
4361
  const user = await store.findByUsername(String(us));
4138
4362
  if (!user) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4139
- if (body.newPass) {
4140
- const newPass = sanitizeString(body.newPass, 128);
4363
+ if (pickAuthField(body, "newPass", "newPassword", "newpass")) {
4364
+ const newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
4141
4365
  const policy = getPasswordPolicy($asai);
4142
- if (body.pass) {
4143
- if (!verifyPassword(String(body.pass), user.passHash, user.passSalt)) {
4366
+ const oldPass = pickAuthField(body, "pass", "password", "pwd");
4367
+ if (oldPass) {
4368
+ if (!verifyPassword(oldPass, user.passHash, user.passSalt)) {
4144
4369
  return sendErr(res, "AUTH_INVALID_PASSWORD", void 0, opt);
4145
4370
  }
4146
4371
  } else if (!user.mustChangePassword) {
@@ -4152,7 +4377,7 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4152
4377
  }
4153
4378
  if (policyErr) return sendErr(res, policyErr, void 0, opt);
4154
4379
  const { passHash, passSalt } = hashPassword(newPass);
4155
- const passHistory = nextPassHistory(String(body.pass || newPass), user.passHistory, policy.historyCount);
4380
+ const passHistory = nextPassHistory(oldPass || newPass, user.passHistory, policy.historyCount);
4156
4381
  await store.updateUser(String(us), {
4157
4382
  passHash,
4158
4383
  passSalt,
@@ -4174,6 +4399,7 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4174
4399
  const ip = getClientIp(req);
4175
4400
  if (url.startsWith("/api/user/login/config/")) {
4176
4401
  const lang = String(req?.headers?.["accept-language"] || opt?.headers?.["accept-language"] || "zh-CN").split(",")[0];
4402
+ await loadUserLevelConfig($asai);
4177
4403
  return sendSuccess(res, getPublicSecurityConfig($asai, lang), opt);
4178
4404
  }
4179
4405
  if (url.startsWith("/api/user/login/admin/levels/")) {
@@ -4187,7 +4413,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4187
4413
  if (!await checkAdmin(req, url)) {
4188
4414
  return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
4189
4415
  }
4190
- const sessions = await store.getSessions();
4416
+ const allSessions = await store.getSessions();
4417
+ const sessions = resolveOnlineSessions($asai, hostdir, allSessions);
4191
4418
  return sendSuccess(
4192
4419
  res,
4193
4420
  {
@@ -4197,7 +4424,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4197
4424
  ip: s.ip,
4198
4425
  createdAt: s.createdAt,
4199
4426
  expiresAt: s.expiresAt,
4200
- lastActivityAt: s.lastActivityAt
4427
+ lastActivityAt: s.lastActivityAt,
4428
+ online: true
4201
4429
  }))
4202
4430
  },
4203
4431
  opt
@@ -4215,12 +4443,13 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4215
4443
  if (!await checkAdmin(req, url)) {
4216
4444
  return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
4217
4445
  }
4218
- const sessions = await store.getSessions();
4446
+ await purgeOfflineSessions($asai, hostdir, store);
4447
+ const onlineCount = await countOnlineSessions($asai, hostdir, store);
4219
4448
  const quota = getQuota2($asai);
4220
4449
  return sendSuccess(
4221
4450
  res,
4222
4451
  {
4223
- sessions: sessions.length,
4452
+ sessions: onlineCount,
4224
4453
  maxLoginSessions: quota.maxLoginSessions ?? 100,
4225
4454
  quotaEnabled: !!quota.enabled,
4226
4455
  http: $asai.connectionshttp,
@@ -4295,10 +4524,10 @@ var As_default = {
4295
4524
  return relativePath;
4296
4525
  },
4297
4526
  //创建多层文件夹 同步
4298
- ensureDir(fs13, relativePath) {
4527
+ ensureDir(fs12, relativePath) {
4299
4528
  const absPathDir = this.toDirPath(relativePath);
4300
4529
  const absPath = this.toAbsolutePath(relativePath);
4301
- if (!fs13.existsSync(absPath)) {
4530
+ if (!fs12.existsSync(absPath)) {
4302
4531
  let pathTmp = "";
4303
4532
  absPathDir.split("/").forEach((el) => {
4304
4533
  if (el) {
@@ -4306,8 +4535,8 @@ var As_default = {
4306
4535
  pathTmp += "/";
4307
4536
  }
4308
4537
  pathTmp += el;
4309
- if (!fs13.existsSync(pathTmp)) {
4310
- if (!fs13.mkdirSync(pathTmp, { recursive: true })) {
4538
+ if (!fs12.existsSync(pathTmp)) {
4539
+ if (!fs12.mkdirSync(pathTmp, { recursive: true })) {
4311
4540
  return absPath;
4312
4541
  }
4313
4542
  }
@@ -4316,11 +4545,11 @@ var As_default = {
4316
4545
  }
4317
4546
  return absPath;
4318
4547
  },
4319
- makeDir(fs13, filePaths) {
4320
- return new Promise((resolve7, reject) => {
4548
+ makeDir(fs12, filePaths) {
4549
+ return new Promise((resolve6, reject) => {
4321
4550
  try {
4322
- const newPath = this.ensureDir(fs13, filePaths);
4323
- resolve7(newPath);
4551
+ const newPath = this.ensureDir(fs12, filePaths);
4552
+ resolve6(newPath);
4324
4553
  } catch (err) {
4325
4554
  reject(err);
4326
4555
  }
@@ -4543,7 +4772,10 @@ var AsAES_default = /* @__PURE__ */ __name(($asai, cfg = null) => {
4543
4772
  return fn(reqData);
4544
4773
  }
4545
4774
  } catch (error) {
4546
- return reqData;
4775
+ if (validateFields(reqData, ["data", "iv", "auth"])) {
4776
+ throw error;
4777
+ }
4778
+ return fn(reqData);
4547
4779
  }
4548
4780
  }
4549
4781
  __name(reqAES, "reqAES");
@@ -4627,6 +4859,48 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4627
4859
  return limittmp;
4628
4860
  }
4629
4861
  __name(reqlimit, "reqlimit");
4862
+ function decodePathSegment(seg) {
4863
+ try {
4864
+ return decodeURIComponent(seg);
4865
+ } catch {
4866
+ return seg;
4867
+ }
4868
+ }
4869
+ __name(decodePathSegment, "decodePathSegment");
4870
+ function bareFileName(name) {
4871
+ const base = String(name).replace(/\.json$/i, "");
4872
+ const slash = base.lastIndexOf("/");
4873
+ return slash > -1 ? base.substring(slash + 1) : base;
4874
+ }
4875
+ __name(bareFileName, "bareFileName");
4876
+ function parseFileBody(raw) {
4877
+ let body = raw;
4878
+ if (typeof body === "string") {
4879
+ try {
4880
+ body = JSON.parse(body);
4881
+ } catch {
4882
+ return { content: body };
4883
+ }
4884
+ }
4885
+ if (body && typeof body === "object" && !Array.isArray(body)) {
4886
+ return {
4887
+ name: body.name != null ? String(body.name) : void 0,
4888
+ content: body.content !== void 0 ? body.content : body
4889
+ };
4890
+ }
4891
+ return { content: body };
4892
+ }
4893
+ __name(parseFileBody, "parseFileBody");
4894
+ function fileContentString(content) {
4895
+ if (content == null) return "";
4896
+ if (typeof content === "string") return content;
4897
+ return JSON.stringify(content);
4898
+ }
4899
+ __name(fileContentString, "fileContentString");
4900
+ function fileKeyFromSegment(seg, bodyName) {
4901
+ return bareFileName(decodePathSegment(seg || bodyName || ""));
4902
+ }
4903
+ __name(fileKeyFromSegment, "fileKeyFromSegment");
4630
4904
  function getSqlParamsByUrl(req, opt) {
4631
4905
  let tmpSqlParams;
4632
4906
  const reqArr = req?.url?.split("?")[0]?.split("/") || [];
@@ -4640,8 +4914,10 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4640
4914
  if (reqArr[4] === "insert") {
4641
4915
  tmpSqlParams.type = reqArr[4];
4642
4916
  if (cfg?.type === "file") {
4917
+ const { name: bodyName, content } = parseFileBody(opt.data);
4918
+ const fileKey = fileKeyFromSegment(reqArr[5], bodyName);
4643
4919
  tmpSqlParams.field = cfg.field.data;
4644
- tmpSqlParams.value = [[reqwherestr(reqArr[5]), reqwherestr(opt.data)]];
4920
+ tmpSqlParams.value = [[fileKey, fileContentString(content)]];
4645
4921
  } else {
4646
4922
  tmpSqlParams.field = [];
4647
4923
  tmpSqlParams.value = [];
@@ -4661,9 +4937,12 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4661
4937
  }
4662
4938
  } else if (reqArr[4] === "update") {
4663
4939
  tmpSqlParams.type = reqArr[4];
4664
- tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(reqArr[5])]];
4940
+ const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
4941
+ tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
4665
4942
  if (cfg?.type === "file") {
4666
- tmpSqlParams.set = [[cfg.field.key, reqwherestr(opt.data)]];
4943
+ const { content } = parseFileBody(opt.data);
4944
+ const contentField = cfg.field.data?.[1] || "content";
4945
+ tmpSqlParams.set = [[contentField, fileContentString(content)]];
4667
4946
  } else {
4668
4947
  tmpSqlParams.set = [];
4669
4948
  const dbData = typeof opt.data === "string" ? JSON.parse(opt.data) : opt.data;
@@ -4679,13 +4958,15 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4679
4958
  }
4680
4959
  } else if (reqArr[4] === "delete") {
4681
4960
  tmpSqlParams.type = reqArr[4];
4682
- tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(reqArr[5])]];
4961
+ const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
4962
+ tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
4683
4963
  } else {
4684
4964
  if (cfg?.as > 1) {
4685
4965
  tmpSqlParams.as = +cfg.as - 1;
4686
4966
  }
4687
4967
  tmpSqlParams.field = cfg.field.data;
4688
- tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(reqArr[5])]];
4968
+ const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
4969
+ tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
4689
4970
  }
4690
4971
  } else {
4691
4972
  tmpSqlParams.as = cfg?.as;
@@ -4701,19 +4982,50 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4701
4982
  if (req?.url?.includes("/selectlist/")) {
4702
4983
  const list = Array.isArray(resdb) ? resdb : resdb?.data;
4703
4984
  if (Array.isArray(list)) {
4704
- const mapped = list.map((el) => {
4985
+ const names = list.map((el) => {
4986
+ if (typeof el === "string") {
4987
+ const dot2 = el.lastIndexOf(".");
4988
+ return dot2 > -1 ? el.substring(0, dot2) : el;
4989
+ }
4705
4990
  const rawName = el.name ?? el.file ?? "";
4706
4991
  let tmpName = String(rawName).substring(String(rawName).lastIndexOf("/") + 1);
4707
4992
  const dot = tmpName.lastIndexOf(".");
4708
4993
  return dot > -1 ? tmpName.substring(0, dot) : tmpName;
4709
4994
  });
4710
- if (Array.isArray(resdb)) return mapped;
4711
- resdb.data = mapped;
4995
+ const children = names.map((name) => ({ name, path: name }));
4996
+ if (Array.isArray(resdb)) return { children };
4997
+ resdb.data = children;
4998
+ return resdb;
4712
4999
  }
4713
5000
  }
4714
5001
  return resdb;
4715
5002
  }
4716
5003
  __name(dbFresh, "dbFresh");
5004
+ function normalizeSelectResult(params, resdb) {
5005
+ let data = resdb;
5006
+ if (params?.where?.length && Array.isArray(data) && data.length === 1) {
5007
+ data = data[0];
5008
+ }
5009
+ if (cfg?.type === "file" && data && typeof data === "object" && !Array.isArray(data)) {
5010
+ const nameKey = cfg.field?.data?.[0] || "name";
5011
+ const contentKey = cfg.field?.data?.[1] || "content";
5012
+ if (data[nameKey] != null) {
5013
+ data[nameKey] = bareFileName(String(data[nameKey]));
5014
+ }
5015
+ const raw = data[contentKey];
5016
+ if (typeof raw === "string") {
5017
+ const text = raw.trim();
5018
+ if (text.startsWith("{") || text.startsWith("[")) {
5019
+ try {
5020
+ data[contentKey] = JSON.parse(text);
5021
+ } catch {
5022
+ }
5023
+ }
5024
+ }
5025
+ }
5026
+ return data;
5027
+ }
5028
+ __name(normalizeSelectResult, "normalizeSelectResult");
4717
5029
  function dbCodePm(req, resdb) {
4718
5030
  if (req?.Userinfo && req?.Userinfo[2]) {
4719
5031
  resdb.data = $asai.$lib.AsCode.asencode(JSON.stringify(resdb.data), req.Userinfo[2]);
@@ -4744,7 +5056,7 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4744
5056
  if (params && typeof DataServer.sqlDb === "function") {
4745
5057
  try {
4746
5058
  DataServer.sqlDb(params).then((resdb) => {
4747
- let resData = dbFresh(req, resdb);
5059
+ let resData = normalizeSelectResult(params, dbFresh(req, resdb));
4748
5060
  if (params?.as === 2 && params?.field?.length > 1) {
4749
5061
  let limitArr = [];
4750
5062
  if (params.limit?.split(",")?.length < 2) {
@@ -5055,19 +5367,31 @@ var lib_default = {
5055
5367
  };
5056
5368
 
5057
5369
  // src/sysserver/db/src/common.ts
5370
+ var common_exports = {};
5371
+ __export(common_exports, {
5372
+ fieldHasId: () => fieldHasId,
5373
+ isPathInside: () => isPathInside,
5374
+ isTableMissingError: () => isTableMissingError,
5375
+ joinTablePath: () => joinTablePath,
5376
+ promisifySqlDb: () => promisifySqlDb,
5377
+ toFieldArray: () => toFieldArray,
5378
+ toInsertRows: () => toInsertRows,
5379
+ toSetObject: () => toSetObject,
5380
+ toWherePairs: () => toWherePairs
5381
+ });
5058
5382
  import * as path5 from "path";
5059
5383
  function promisifySqlDb(query, sql) {
5060
- return new Promise((resolve7, reject) => {
5384
+ return new Promise((resolve6, reject) => {
5061
5385
  query(sql, (err, result) => {
5062
5386
  if (err) {
5063
5387
  reject(err);
5064
5388
  return;
5065
5389
  }
5066
5390
  if (!result || Array.isArray(result) && result.length === 0) {
5067
- resolve7("");
5391
+ resolve6("");
5068
5392
  return;
5069
5393
  }
5070
- resolve7(result);
5394
+ resolve6(result);
5071
5395
  });
5072
5396
  });
5073
5397
  }
@@ -5077,6 +5401,48 @@ function toFieldArray(field) {
5077
5401
  return typeof field === "string" ? field.split(",") : field;
5078
5402
  }
5079
5403
  __name(toFieldArray, "toFieldArray");
5404
+ function toWherePairs(where) {
5405
+ if (!where || !Array.isArray(where)) return [];
5406
+ const pairs = [];
5407
+ for (const item of where) {
5408
+ if (typeof item === "string") continue;
5409
+ if (Array.isArray(item) && item.length >= 3) {
5410
+ pairs.push([item[0], item[2]]);
5411
+ }
5412
+ }
5413
+ return pairs;
5414
+ }
5415
+ __name(toWherePairs, "toWherePairs");
5416
+ function toSetObject(set) {
5417
+ if (!set) return {};
5418
+ if (typeof set === "string") return {};
5419
+ const obj = {};
5420
+ for (const item of set) {
5421
+ if (typeof item === "string") continue;
5422
+ if (Array.isArray(item)) {
5423
+ if (item.length >= 3 && typeof item[1] === "string") {
5424
+ obj[item[0]] = item[2];
5425
+ } else if (item.length >= 2) {
5426
+ obj[item[0]] = item[1];
5427
+ }
5428
+ }
5429
+ }
5430
+ return obj;
5431
+ }
5432
+ __name(toSetObject, "toSetObject");
5433
+ function toInsertRows(sql) {
5434
+ const fields = toFieldArray(sql.field);
5435
+ const values = sql.value;
5436
+ if (!fields.length || !Array.isArray(values)) return [];
5437
+ return values.map((row) => {
5438
+ const rowObj = {};
5439
+ fields.forEach((field, index) => {
5440
+ rowObj[field] = row[index];
5441
+ });
5442
+ return rowObj;
5443
+ });
5444
+ }
5445
+ __name(toInsertRows, "toInsertRows");
5080
5446
  function isPathInside(baseDir, targetPath) {
5081
5447
  const base = path5.resolve(baseDir);
5082
5448
  const target = path5.resolve(targetPath);
@@ -5262,936 +5628,426 @@ var Sql_default = {
5262
5628
  }
5263
5629
  };
5264
5630
 
5265
- // src/sysserver/db/src/mysql/Index.ts
5266
- import mysql from "mysql";
5267
- var DbMySQL = class {
5268
- static {
5269
- __name(this, "DbMySQL");
5270
- }
5271
- constructor(config) {
5272
- this.config = config;
5273
- this.pool = mysql.createPool(config);
5274
- }
5275
- getErr(sql, err) {
5276
- if (this.config.create && isTableMissingError(err)) {
5277
- this.createTable(sql);
5278
- }
5279
- return err?.message || null;
5280
- }
5281
- getResult(rows) {
5282
- return rows ?? null;
5283
- }
5284
- query(sql, callback) {
5285
- this.pool.getConnection((error, connection) => {
5286
- if (error) {
5287
- callback(this.getErr(sql, error), this.getResult(null));
5288
- return;
5289
- }
5290
- connection.query(Sql_default.makeSql(sql), (err, rows) => {
5291
- try {
5292
- callback(this.getErr(sql, err), this.getResult(rows));
5293
- } finally {
5294
- connection.release();
5295
- }
5296
- });
5297
- });
5298
- }
5299
- sqlDb(sql) {
5300
- return promisifySqlDb(this.query.bind(this), sql);
5301
- }
5302
- /** 关闭连接池,释放所有连接 */
5303
- close(callback) {
5304
- this.pool.end(callback);
5305
- }
5306
- /** 表不存在时自动创建数据库与表(需 config.create 为 true) */
5307
- createTable(sql) {
5308
- if (!this.config.create) return;
5309
- const conn = mysql.createConnection({
5310
- host: this.config.host,
5311
- user: this.config.user,
5312
- password: this.config.password
5313
- });
5314
- conn.connect((err) => {
5315
- if (err) {
5316
- conn.end();
5317
- return;
5318
- }
5319
- conn.query(`CREATE DATABASE IF NOT EXISTS ${this.config.database};`, (dbErr) => {
5320
- if (dbErr) {
5321
- conn.end();
5322
- return;
5323
- }
5324
- this.pool = mysql.createPool(this.config);
5325
- this.pool.getConnection((poolErr, connection) => {
5326
- if (poolErr || !connection) {
5327
- conn.end();
5328
- return;
5329
- }
5330
- connection.query(Sql_default.createSql(sql, "mysql"), () => {
5331
- connection.release();
5332
- conn.end();
5333
- });
5334
- });
5335
- });
5336
- });
5337
- }
5338
- };
5339
- var Index_default2 = DbMySQL;
5340
-
5341
- // src/sysserver/db/src/sqlite/Index.ts
5631
+ // src/sysserver/db/src/file/Index.ts
5342
5632
  import * as fs11 from "fs";
5343
5633
  import * as path6 from "path";
5344
- import sqlite3Import from "sqlite3";
5345
- var sqlite3 = sqlite3Import.default ?? sqlite3Import;
5346
- var DEFAULT_BUSY_TIMEOUT_MS = 1e4;
5347
- var DEFAULT_ACQUIRE_TIMEOUT_MS = 15e3;
5348
- function configureSqliteConnection(db, busyTimeoutMs = DEFAULT_BUSY_TIMEOUT_MS) {
5349
- const busyMs = Math.max(0, Math.floor(busyTimeoutMs));
5350
- return new Promise((resolve7, reject) => {
5351
- db.serialize(() => {
5352
- db.run("PRAGMA journal_mode = WAL");
5353
- db.run(`PRAGMA busy_timeout = ${busyMs}`);
5354
- db.run("PRAGMA synchronous = NORMAL", (err) => {
5355
- if (err) reject(err);
5356
- else resolve7();
5357
- });
5358
- });
5359
- });
5360
- }
5361
- __name(configureSqliteConnection, "configureSqliteConnection");
5362
- var PooledConnection = class _PooledConnection {
5363
- static {
5364
- __name(this, "PooledConnection");
5365
- }
5366
- // 连接是否有效
5367
- constructor(db, config) {
5368
- this.db = db;
5369
- this.config = config;
5370
- this.lastUsed = Date.now();
5371
- this.inUse = false;
5372
- this.isValid = true;
5373
- }
5374
- /**
5375
- * 异步创建连接,确保目录存在并打开数据库
5376
- */
5377
- static async create(config) {
5378
- function dbLog(...args) {
5379
- (config?.$asai?.$log || console.log)("SQLITE", ...args);
5380
- }
5381
- __name(dbLog, "dbLog");
5382
- const absolutePath = path6.resolve(config.database);
5383
- const dir = path6.dirname(absolutePath);
5384
- if (!fs11.existsSync(dir)) {
5385
- fs11.mkdirSync(dir, { recursive: true });
5386
- dbLog("MKDIR", dir);
5387
- }
5388
- return new Promise((resolve7, reject) => {
5389
- const db = new sqlite3.Database(absolutePath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, async (err) => {
5390
- if (err) {
5391
- dbLog("open failed", "[ERROR] Failed to open database:", absolutePath, err.message);
5392
- reject(err);
5393
- return;
5394
- }
5395
- try {
5396
- await configureSqliteConnection(db, config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS);
5397
- dbLog("OPEN", absolutePath);
5398
- resolve7(new _PooledConnection(db, config));
5399
- } catch (pragmaErr) {
5400
- db.close(() => {
5401
- });
5402
- reject(pragmaErr);
5403
- }
5404
- });
5405
- });
5406
- }
5407
- /** 测试连接是否有效(执行 SELECT 1) */
5408
- async test() {
5409
- return new Promise((resolve7) => {
5410
- if (!this.isValid) return resolve7(false);
5411
- this.db.get("SELECT 1", (err) => {
5412
- this.isValid = !err;
5413
- resolve7(this.isValid);
5414
- });
5415
- });
5416
- }
5417
- /** 关闭连接 */
5418
- async close() {
5419
- return new Promise((resolve7, reject) => {
5420
- this.db.close((err) => {
5421
- if (err) reject(err);
5422
- else resolve7();
5423
- });
5424
- });
5425
- }
5426
- /** 标记连接为无效 */
5427
- invalidate() {
5428
- this.isValid = false;
5429
- }
5430
- };
5431
- var SQLitePool = class {
5432
- // 初始化完成 Promise
5433
- constructor(config) {
5434
- this.pool = [];
5435
- this.waitingQueue = [];
5436
- this.idleCheckerTimer = null;
5437
- this.isDestroyed = false;
5438
- this.config = {
5439
- database: config.database,
5440
- poolMin: config.poolMin ?? 1,
5441
- poolMax: config.poolMax ?? 1,
5442
- idleTimeout: config.idleTimeout ?? 3e4,
5443
- acquireTimeout: config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT_MS,
5444
- busyTimeout: config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS,
5445
- enableCreateTable: config.enableCreateTable ?? true,
5446
- $asai: config.$asai ?? null
5447
- };
5448
- this.initPromise = this.initializePool();
5449
- this.startIdleChecker();
5450
- }
5451
- static {
5452
- __name(this, "SQLitePool");
5453
- }
5454
- /** 统一日志输出(前缀 ♣ 标识来自 SQLite 连接池模块) */
5455
- dbLog(...args) {
5456
- (this.config?.$asai?.$log || console.log)("sqlite", ...args);
5457
- }
5458
- /** 初始化连接池,创建最小连接数 */
5459
- async initializePool() {
5460
- const promises = [];
5461
- for (let i = 0; i < this.config.poolMin; i++) {
5462
- promises.push(this.createConnection());
5463
- }
5464
- const results = await Promise.allSettled(promises);
5465
- const successCount = results.filter((r) => r.status === "fulfilled").length;
5466
- if (successCount === 0) {
5467
- this.dbLog("Initialization failed", "[POOL] Initialization failed: no connections created");
5468
- } else {
5469
- this.dbLog("Initialized", "[POOL] Initialized, size:", this.pool.length);
5470
- }
5471
- }
5472
- /** 创建一个新连接并加入池中 */
5473
- async createConnection() {
5474
- if (this.pool.length >= this.config.poolMax) return;
5475
- try {
5476
- const conn = await PooledConnection.create(this.config);
5477
- const isValid = await conn.test();
5478
- if (isValid) {
5479
- this.pool.push(conn);
5480
- } else {
5481
- await conn.close().catch(() => {
5482
- });
5483
- throw new Error("Connection test failed");
5634
+ function dbDriver(opt = {}) {
5635
+ const { Sql, DbCommon } = opt;
5636
+ const { isPathInside: isPathInside2, joinTablePath: joinTablePath2 } = DbCommon;
5637
+ class DbFile {
5638
+ static {
5639
+ __name(this, "DbFile");
5640
+ }
5641
+ constructor(config) {
5642
+ this.config = config;
5643
+ this.fileExt = config.ext === ".*" ? "" : config.ext || "";
5644
+ this.fileDel = (config.del || 0) !== 0;
5645
+ this.createDir = config.create || false;
5646
+ this.baseDirAbs = path6.resolve(process.cwd(), config.dir);
5647
+ }
5648
+ dbLog(...args) {
5649
+ (this.config?.$asai?.$log || console.log)("FILE", ...args);
5650
+ }
5651
+ checkFile(file) {
5652
+ return fs11.existsSync(this.getAbsPath(file, false));
5653
+ }
5654
+ /** 解析相对路径并限制在数据根目录内,防止路径穿越(跨平台) */
5655
+ resolvePath(relPath) {
5656
+ if (!relPath) {
5657
+ return this.baseDirAbs;
5658
+ }
5659
+ const abs = path6.isAbsolute(relPath) ? path6.normalize(relPath) : path6.resolve(this.baseDirAbs, relPath);
5660
+ if (!isPathInside2(this.baseDirAbs, abs)) {
5661
+ return this.baseDirAbs;
5662
+ }
5663
+ return abs;
5664
+ }
5665
+ /** 将 table 与文件名片段拼为平台无关相对路径 */
5666
+ tableFilePath(table, ...segments) {
5667
+ return joinTablePath2(table, ...segments);
5668
+ }
5669
+ normalizeRelPath(relPath) {
5670
+ if (!relPath) {
5671
+ return "";
5484
5672
  }
5485
- } catch (err) {
5486
- this.dbLog("Database create failed", "[CONN] Failed to create connection:", err.message);
5487
- throw err;
5673
+ return path6.isAbsolute(relPath) ? path6.relative(this.baseDirAbs, relPath) : relPath;
5488
5674
  }
5489
- }
5490
- /**
5491
- * 获取一个可用连接
5492
- * - 优先返回空闲连接
5493
- * - 若连接数未达上限则创建新连接
5494
- * - 否则进入等待队列
5495
- */
5496
- async acquire() {
5497
- await this.initPromise;
5498
- if (this.isDestroyed) {
5499
- throw new Error("Pool has been destroyed");
5675
+ ensureDir(relPath) {
5676
+ const relative4 = this.normalizeRelPath(relPath);
5677
+ const dirPath = path6.dirname(relative4);
5678
+ const absDir = this.resolvePath(dirPath === "." ? "" : dirPath);
5679
+ if (this.createDir && !fs11.existsSync(absDir)) {
5680
+ this.dbLog("mkdir", "[MKDIR]", absDir);
5681
+ fs11.mkdirSync(absDir, { recursive: true });
5682
+ }
5683
+ return absDir;
5500
5684
  }
5501
- const now = Date.now();
5502
- const staleCheckMs = Math.min(this.config.idleTimeout, 5e3);
5503
- for (const conn of this.pool) {
5504
- if (!conn.inUse) {
5505
- if (now - conn.lastUsed > staleCheckMs) {
5506
- const isValid = await conn.test();
5507
- if (!isValid) {
5508
- this.removeConnection(conn);
5509
- continue;
5510
- }
5511
- }
5512
- conn.inUse = true;
5513
- conn.lastUsed = now;
5514
- return conn;
5685
+ getAbsPath(file, autoCreateDir = true) {
5686
+ let rel = file;
5687
+ if (this.fileExt && !rel.endsWith(this.fileExt)) {
5688
+ rel += this.fileExt;
5689
+ }
5690
+ if (autoCreateDir) {
5691
+ this.ensureDir(rel);
5515
5692
  }
5693
+ return this.resolvePath(rel);
5516
5694
  }
5517
- if (this.pool.length < this.config.poolMax) {
5518
- try {
5519
- const conn = await PooledConnection.create(this.config);
5520
- const isValid = await conn.test();
5521
- if (isValid) {
5522
- conn.inUse = true;
5523
- conn.lastUsed = Date.now();
5524
- this.pool.push(conn);
5525
- return conn;
5695
+ deldir(relativePath) {
5696
+ const absPath = this.resolvePath(relativePath);
5697
+ if (!fs11.existsSync(absPath)) {
5698
+ return;
5699
+ }
5700
+ this.dbLog("delete", "[RMDIR]", absPath);
5701
+ const entries = fs11.readdirSync(absPath, { withFileTypes: true });
5702
+ for (const entry of entries) {
5703
+ const current = path6.join(absPath, entry.name);
5704
+ if (entry.isDirectory()) {
5705
+ this.deldir(path6.join(relativePath, entry.name));
5526
5706
  } else {
5527
- await conn.close().catch(() => {
5528
- });
5707
+ this.dbLog("delete", "[UNLINK]", current);
5708
+ fs11.unlinkSync(current);
5529
5709
  }
5530
- } catch (e) {
5531
- this.dbLog("create failed", "[ACQUIRE] Failed to create new connection, waiting:", e.message);
5532
5710
  }
5711
+ fs11.rmdirSync(absPath);
5533
5712
  }
5534
- return new Promise((resolve7, reject) => {
5535
- const timer = setTimeout(() => {
5536
- const idx = this.waitingQueue.findIndex((item) => item.timer === timer);
5537
- if (idx !== -1) {
5538
- this.waitingQueue.splice(idx, 1);
5539
- }
5540
- reject(new Error(`Acquire timeout (${this.config.acquireTimeout}ms)`));
5541
- }, this.config.acquireTimeout);
5542
- this.waitingQueue.push({ resolve: resolve7, reject, timer });
5543
- });
5544
- }
5545
- /** 释放连接回池中 */
5546
- release(conn) {
5547
- if (!conn) return;
5548
- conn.inUse = false;
5549
- conn.lastUsed = Date.now();
5550
- if (this.waitingQueue.length > 0) {
5551
- const next = this.waitingQueue.shift();
5552
- clearTimeout(next.timer);
5553
- conn.inUse = true;
5554
- conn.lastUsed = Date.now();
5555
- next.resolve(conn);
5556
- }
5557
- }
5558
- /** 从池中移除并关闭指定连接 */
5559
- removeConnection(conn) {
5560
- const index = this.pool.indexOf(conn);
5561
- if (index !== -1) {
5562
- this.pool.splice(index, 1);
5563
- conn.close().catch(() => {
5564
- });
5565
- }
5566
- }
5567
- /** 启动空闲连接回收定时器(每 60 秒检查一次) */
5568
- startIdleChecker() {
5569
- this.idleCheckerTimer = setInterval(async () => {
5570
- if (this.isDestroyed) return;
5571
- const now = Date.now();
5572
- const toRemove = [];
5573
- for (const conn of this.pool) {
5574
- if (!conn.inUse && now - conn.lastUsed > this.config.idleTimeout && this.pool.length > this.config.poolMin) {
5575
- toRemove.push(conn);
5713
+ getReadPath(file, sql) {
5714
+ const candidates = [];
5715
+ if (sql?.filename) {
5716
+ candidates.push({ dir: path6.join(file, sql.filename) });
5717
+ candidates.push({ dir: path6.join(file + ".delete" + this.fileExt, sql.filename) });
5718
+ } else {
5719
+ candidates.push({ dir: file });
5720
+ candidates.push({ dir: file + this.fileExt + ".delete" });
5721
+ }
5722
+ for (const candidate of candidates) {
5723
+ const abs = this.getAbsPath(candidate.dir, false);
5724
+ if (fs11.existsSync(abs)) {
5725
+ return { path: abs, dir: candidate.dir };
5576
5726
  }
5577
5727
  }
5578
- for (const conn of toRemove) {
5579
- this.removeConnection(conn);
5728
+ return { path: "", dir: "" };
5729
+ }
5730
+ getDelPath(file, sql) {
5731
+ const read2 = this.getReadPath(file, sql);
5732
+ if (!read2.path) {
5733
+ return { path: "", dir: read2.dir };
5580
5734
  }
5581
- if (toRemove.length > 0) {
5582
- this.dbLog("Reclaimed", "[IDLE] Reclaimed", toRemove.length, "idle connections, current pool size:", this.pool.length);
5735
+ if (sql?.filename) {
5736
+ return { path: path6.dirname(read2.path), dir: path6.dirname(read2.dir) };
5583
5737
  }
5584
- }, 6e4);
5585
- }
5586
- /** 获取连接池当前状态 */
5587
- getStatus() {
5588
- const inUse = this.pool.filter((c) => c.inUse).length;
5589
- return {
5590
- total: this.pool.length,
5591
- inUse,
5592
- idle: this.pool.length - inUse,
5593
- waiting: this.waitingQueue.length,
5594
- min: this.config.poolMin,
5595
- max: this.config.poolMax
5596
- };
5597
- }
5598
- /** 销毁连接池,释放所有资源 */
5599
- async destroy() {
5600
- if (this.isDestroyed) return;
5601
- this.isDestroyed = true;
5602
- if (this.idleCheckerTimer) {
5603
- clearInterval(this.idleCheckerTimer);
5604
- this.idleCheckerTimer = null;
5605
- }
5606
- for (const waiter of this.waitingQueue) {
5607
- clearTimeout(waiter.timer);
5608
- waiter.reject(new Error("Pool has been closed"));
5738
+ return { path: read2.path, dir: read2.dir };
5609
5739
  }
5610
- this.waitingQueue = [];
5611
- await Promise.allSettled(this.pool.map((conn) => conn.close()));
5612
- this.pool = [];
5613
- this.dbLog("Destroyed", "[POOL] Destroyed");
5614
- }
5615
- };
5616
- var DbSQLite3 = class {
5617
- constructor(config) {
5618
- this.pool = null;
5619
- this.singleDb = null;
5620
- this.singleDbReady = null;
5621
- /** 串行化同库 CRUD,避免 node-sqlite3 回调并发导致锁等待与连接泄漏 */
5622
- this.opChain = Promise.resolve();
5623
- this.config = config;
5624
- const verbose = sqlite3.verbose();
5625
- if (config.usePool === true) {
5626
- this.pool = new SQLitePool({
5627
- database: config.database,
5628
- poolMin: config.poolMin ?? 1,
5629
- poolMax: config.poolMax ?? 1,
5630
- idleTimeout: config.idleTimeout,
5631
- acquireTimeout: config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT_MS,
5632
- busyTimeout: config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS,
5633
- enableCreateTable: config.create,
5634
- $asai: config.$asai
5635
- });
5636
- } else {
5637
- this.singleDbReady = this.initSingleConnection(config.database, verbose);
5740
+ getNewPath(file, sql) {
5741
+ if (sql?.filename) {
5742
+ return this.getAbsPath(path6.join(file, sql.filename), true);
5743
+ }
5744
+ return this.getAbsPath(file, true);
5638
5745
  }
5639
- }
5640
- static {
5641
- __name(this, "DbSQLite3");
5642
- }
5643
- runSerialized(task) {
5644
- const run = this.opChain.then(() => task());
5645
- this.opChain = run.then(
5646
- () => void 0,
5647
- () => void 0
5648
- );
5649
- return run;
5650
- }
5651
- dbLog(...args) {
5652
- (this.config?.$asai?.$log || console.log)("sqlite", ...args);
5653
- }
5654
- async initSingleConnection(databasePath, verbose) {
5655
- const absolutePath = path6.resolve(databasePath);
5656
- const dir = path6.dirname(absolutePath);
5657
- if (!fs11.existsSync(dir)) {
5658
- fs11.mkdirSync(dir, { recursive: true });
5659
- this.dbLog("mkdir", "[MKDIR]", dir);
5660
- }
5661
- return new Promise((resolve7, reject) => {
5662
- this.singleDb = new verbose.Database(absolutePath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, async (err) => {
5663
- if (err) {
5664
- this.dbLog("open failed", "[ERROR] Failed to open single connection:", absolutePath, err.message);
5665
- reject(err);
5666
- return;
5667
- }
5668
- try {
5669
- await configureSqliteConnection(this.singleDb, this.config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS);
5670
- this.dbLog("open", "[OPEN] Single connection:", absolutePath);
5671
- resolve7();
5672
- } catch (pragmaErr) {
5673
- this.singleDb.close(() => {
5674
- });
5675
- reject(pragmaErr);
5676
- }
5677
- });
5678
- });
5679
- }
5680
- getErr(_sql, err) {
5681
- return err?.message || null;
5682
- }
5683
- getResult(rows) {
5684
- return rows ?? "OK";
5685
- }
5686
- /**
5687
- * 执行 SQL 查询(回调风格)
5688
- */
5689
- query(sql, callback) {
5690
- const sqlStr = Sql_default.makeSql(sql);
5691
- const executeQuery = /* @__PURE__ */ __name((db, release) => {
5692
- const finish = /* @__PURE__ */ __name((err, rows) => {
5693
- release?.();
5694
- callback(this.getErr(sql, err), this.getResult(rows));
5695
- }, "finish");
5746
+ write(file, data, sql) {
5696
5747
  try {
5697
- if (sql.type === "select") {
5698
- db.all(sqlStr, finish);
5699
- } else {
5700
- db.run(sqlStr, (err) => {
5701
- finish(err, "OK");
5702
- });
5703
- }
5748
+ const filePath = sql?.type === "insert" ? this.getNewPath(file, sql) : this.getReadPath(file, sql).path || this.getNewPath(file, sql);
5749
+ this.dbLog("write", "[WRITE]", filePath);
5750
+ fs11.writeFileSync(filePath, data, "utf8");
5751
+ return "ok";
5704
5752
  } catch (e) {
5705
- release?.();
5706
- callback(this.getErr(sql, e), this.getResult("sql error"));
5707
- }
5708
- }, "executeQuery");
5709
- if (this.pool) {
5710
- this.pool.acquire().then((conn) => {
5711
- executeQuery(conn.db, () => this.pool.release(conn));
5712
- }).catch((err) => {
5713
- callback(this.getErr(sql, err), this.getResult("pool error"));
5714
- });
5715
- } else {
5716
- (this.singleDbReady || Promise.resolve()).then(() => {
5717
- executeQuery(this.singleDb);
5718
- }).catch((err) => {
5719
- callback(this.getErr(sql, err), this.getResult("connection error"));
5720
- });
5721
- }
5722
- }
5723
- /** 执行 SQL 操作(Promise 风格,串行 + 缺表时自动建表并重试一次) */
5724
- sqlDb(sql) {
5725
- return this.runSerialized(() => this.executeSqlDb(sql));
5726
- }
5727
- async executeSqlDb(sql) {
5728
- try {
5729
- return await promisifySqlDb(this.query.bind(this), sql);
5730
- } catch (err) {
5731
- const errObj = { message: String(err?.message || err || "") };
5732
- if (this.config.create && sql.type === "insert" && isTableMissingError(errObj)) {
5733
- await this.createTable(sql);
5734
- return promisifySqlDb(this.query.bind(this), sql);
5753
+ this.dbLog("write failed", "[WRITE ERROR]", e);
5754
+ return e;
5735
5755
  }
5736
- throw err;
5737
5756
  }
5738
- }
5739
- /**
5740
- * 自动建表(当表不存在时触发)
5741
- */
5742
- async createTable(sql) {
5743
- const createSql = Sql_default.createSql(sql, "sqlite");
5744
- if (!createSql) return;
5745
- if (this.pool) {
5746
- let conn = null;
5757
+ read(file, sql, encoding = "utf8") {
5747
5758
  try {
5748
- conn = await this.pool.acquire();
5749
- await new Promise((resolve7, reject) => {
5750
- conn.db.run(createSql, (err) => {
5751
- if (err) reject(err);
5752
- else resolve7();
5753
- });
5754
- });
5755
- this.dbLog("Created", "[TABLE] Created:", createSql);
5759
+ const read2 = this.getReadPath(file, sql);
5760
+ if (!read2.path) {
5761
+ return { error: "empty" };
5762
+ }
5763
+ this.dbLog("read", "[READ]", read2.path);
5764
+ const data = fs11.readFileSync(read2.path, { encoding }) || "";
5765
+ const fileName = this.fileExt && read2.dir.endsWith(this.fileExt) ? read2.dir : read2.dir + this.fileExt;
5766
+ return [{ data, file: fileName }];
5756
5767
  } catch (e) {
5757
- this.dbLog("Creation failed", "[TABLE] Creation failed:", e);
5758
- } finally {
5759
- if (conn) this.pool.release(conn);
5768
+ this.dbLog("read failed", "[READ ERROR]", e);
5769
+ return e;
5760
5770
  }
5761
- } else {
5762
- await this.singleDbReady;
5763
- await new Promise((resolve7, reject) => {
5764
- this.singleDb.run(createSql, (err) => {
5765
- if (err) reject(err);
5766
- else resolve7();
5767
- });
5768
- });
5769
- }
5770
- }
5771
- /** 获取连接池状态(仅连接池模式有效) */
5772
- getPoolStatus() {
5773
- if (this.pool) {
5774
- return this.pool.getStatus();
5775
- }
5776
- return { mode: "single-connection" };
5777
- }
5778
- /** 关闭所有连接 */
5779
- async close() {
5780
- if (this.pool) {
5781
- await this.pool.destroy();
5782
- } else if (this.singleDb) {
5783
- await new Promise((resolve7, reject) => {
5784
- this.singleDb.close((err) => {
5785
- if (err) reject(err);
5786
- else resolve7();
5787
- });
5788
- });
5789
- }
5790
- }
5791
- };
5792
- var Index_default3 = DbSQLite3;
5793
-
5794
- // src/sysserver/db/src/file/Index.ts
5795
- import * as fs12 from "fs";
5796
- import * as path7 from "path";
5797
- var DbFile = class {
5798
- static {
5799
- __name(this, "DbFile");
5800
- }
5801
- constructor(config) {
5802
- this.config = config;
5803
- this.fileExt = config.ext === ".*" ? "" : config.ext || "";
5804
- this.fileDel = (config.del || 0) !== 0;
5805
- this.createDir = config.create || false;
5806
- this.baseDirAbs = path7.resolve(process.cwd(), config.dir);
5807
- }
5808
- dbLog(...args) {
5809
- (this.config?.$asai?.$log || console.log)("FILE", ...args);
5810
- }
5811
- checkFile(file) {
5812
- return fs12.existsSync(this.getAbsPath(file, false));
5813
- }
5814
- /** 解析相对路径并限制在数据根目录内,防止路径穿越(跨平台) */
5815
- resolvePath(relPath) {
5816
- if (!relPath) {
5817
- return this.baseDirAbs;
5818
- }
5819
- const abs = path7.isAbsolute(relPath) ? path7.normalize(relPath) : path7.resolve(this.baseDirAbs, relPath);
5820
- if (!isPathInside(this.baseDirAbs, abs)) {
5821
- return this.baseDirAbs;
5822
- }
5823
- return abs;
5824
- }
5825
- /** 将 table 与文件名片段拼为平台无关相对路径 */
5826
- tableFilePath(table, ...segments) {
5827
- return joinTablePath(table, ...segments);
5828
- }
5829
- normalizeRelPath(relPath) {
5830
- if (!relPath) {
5831
- return "";
5832
- }
5833
- return path7.isAbsolute(relPath) ? path7.relative(this.baseDirAbs, relPath) : relPath;
5834
- }
5835
- ensureDir(relPath) {
5836
- const relative4 = this.normalizeRelPath(relPath);
5837
- const dirPath = path7.dirname(relative4);
5838
- const absDir = this.resolvePath(dirPath === "." ? "" : dirPath);
5839
- if (this.createDir && !fs12.existsSync(absDir)) {
5840
- this.dbLog("mkdir", "[MKDIR]", absDir);
5841
- fs12.mkdirSync(absDir, { recursive: true });
5842
- }
5843
- return absDir;
5844
- }
5845
- getAbsPath(file, autoCreateDir = true) {
5846
- let rel = file;
5847
- if (this.fileExt && !rel.endsWith(this.fileExt)) {
5848
- rel += this.fileExt;
5849
- }
5850
- if (autoCreateDir) {
5851
- this.ensureDir(rel);
5852
- }
5853
- return this.resolvePath(rel);
5854
- }
5855
- deldir(relativePath) {
5856
- const absPath = this.resolvePath(relativePath);
5857
- if (!fs12.existsSync(absPath)) {
5858
- return;
5859
- }
5860
- this.dbLog("delete", "[RMDIR]", absPath);
5861
- const entries = fs12.readdirSync(absPath, { withFileTypes: true });
5862
- for (const entry of entries) {
5863
- const current = path7.join(absPath, entry.name);
5864
- if (entry.isDirectory()) {
5865
- this.deldir(path7.join(relativePath, entry.name));
5866
- } else {
5867
- this.dbLog("delete", "[UNLINK]", current);
5868
- fs12.unlinkSync(current);
5869
- }
5870
- }
5871
- fs12.rmdirSync(absPath);
5872
- }
5873
- getReadPath(file, sql) {
5874
- const candidates = [];
5875
- if (sql?.filename) {
5876
- candidates.push({ dir: path7.join(file, sql.filename) });
5877
- candidates.push({ dir: path7.join(file + ".delete" + this.fileExt, sql.filename) });
5878
- } else {
5879
- candidates.push({ dir: file });
5880
- candidates.push({ dir: file + this.fileExt + ".delete" });
5881
- }
5882
- for (const candidate of candidates) {
5883
- const abs = this.getAbsPath(candidate.dir, false);
5884
- if (fs12.existsSync(abs)) {
5885
- return { path: abs, dir: candidate.dir };
5886
- }
5887
- }
5888
- return { path: "", dir: "" };
5889
- }
5890
- getDelPath(file, sql) {
5891
- const read2 = this.getReadPath(file, sql);
5892
- if (!read2.path) {
5893
- return { path: "", dir: read2.dir };
5894
5771
  }
5895
- if (sql?.filename) {
5896
- return { path: path7.dirname(read2.path), dir: path7.dirname(read2.dir) };
5897
- }
5898
- return { path: read2.path, dir: read2.dir };
5899
- }
5900
- getNewPath(file, sql) {
5901
- if (sql?.filename) {
5902
- return this.getAbsPath(path7.join(file, sql.filename), true);
5903
- }
5904
- return this.getAbsPath(file, true);
5905
- }
5906
- write(file, data, sql) {
5907
- try {
5908
- const filePath = sql?.type === "insert" ? this.getNewPath(file, sql) : this.getReadPath(file, sql).path || this.getNewPath(file, sql);
5909
- this.dbLog("write", "[WRITE]", filePath);
5910
- fs12.writeFileSync(filePath, data, "utf8");
5911
- return "ok";
5912
- } catch (e) {
5913
- this.dbLog("write failed", "[WRITE ERROR]", e);
5914
- return e;
5915
- }
5916
- }
5917
- read(file, sql, encoding = "utf8") {
5918
- try {
5919
- const read2 = this.getReadPath(file, sql);
5920
- if (!read2.path) {
5921
- return { error: "empty" };
5922
- }
5923
- this.dbLog("read", "[READ]", read2.path);
5924
- const data = fs12.readFileSync(read2.path, { encoding }) || "";
5925
- const fileName = this.fileExt && read2.dir.endsWith(this.fileExt) ? read2.dir : read2.dir + this.fileExt;
5926
- return [{ data, file: fileName }];
5927
- } catch (e) {
5928
- this.dbLog("read failed", "[READ ERROR]", e);
5929
- return e;
5930
- }
5931
- }
5932
- del(file, sql) {
5933
- try {
5934
- const del = this.getDelPath(file, sql);
5935
- if (!del.path) {
5936
- return "";
5937
- }
5938
- if (this.fileDel && sql?.deltrue) {
5939
- const target = del.path + ".delete" + this.fileExt;
5940
- this.dbLog("soft delete", "[SOFT DELETE]", `${del.path} -> ${target}`);
5941
- fs12.renameSync(del.path, target);
5942
- } else if (sql?.filename) {
5943
- this.deldir(del.dir);
5944
- } else {
5945
- this.dbLog("delete", "[DELETE]", del.path);
5946
- fs12.unlinkSync(del.path);
5772
+ del(file, sql) {
5773
+ try {
5774
+ const del = this.getDelPath(file, sql);
5775
+ if (!del.path) {
5776
+ return "";
5777
+ }
5778
+ if (this.fileDel && sql?.deltrue) {
5779
+ const target = del.path + ".delete" + this.fileExt;
5780
+ this.dbLog("soft delete", "[SOFT DELETE]", `${del.path} -> ${target}`);
5781
+ fs11.renameSync(del.path, target);
5782
+ } else if (sql?.filename) {
5783
+ this.deldir(del.dir);
5784
+ } else {
5785
+ this.dbLog("delete", "[DELETE]", del.path);
5786
+ fs11.unlinkSync(del.path);
5787
+ }
5788
+ return "ok";
5789
+ } catch (e) {
5790
+ this.dbLog("delete failed", "[DELETE ERROR]", e);
5791
+ return e;
5947
5792
  }
5948
- return "ok";
5949
- } catch (e) {
5950
- this.dbLog("delete failed", "[DELETE ERROR]", e);
5951
- return e;
5952
5793
  }
5953
- }
5954
- re(file, sql) {
5955
- try {
5956
- const del = this.getDelPath(file, sql);
5957
- if (!del.path) {
5958
- return "";
5959
- }
5960
- if (this.fileDel) {
5961
- const extPattern = this.fileExt ? this.fileExt.replace(/\./g, "\\.") : "";
5962
- const original = del.path.replace(new RegExp(`.delete${extPattern}$`), "");
5963
- if (original !== del.path) {
5964
- this.dbLog("restore", "[RESTORE]", `${del.path} -> ${original}`);
5965
- fs12.renameSync(del.path, original);
5794
+ re(file, sql) {
5795
+ try {
5796
+ const del = this.getDelPath(file, sql);
5797
+ if (!del.path) {
5798
+ return "";
5966
5799
  }
5967
- } else {
5968
- this.dbLog("force delete", "[FORCE DELETE]", del.path);
5969
- fs12.unlinkSync(del.path);
5800
+ if (this.fileDel) {
5801
+ const extPattern = this.fileExt ? this.fileExt.replace(/\./g, "\\.") : "";
5802
+ const original = del.path.replace(new RegExp(`.delete${extPattern}$`), "");
5803
+ if (original !== del.path) {
5804
+ this.dbLog("restore", "[RESTORE]", `${del.path} -> ${original}`);
5805
+ fs11.renameSync(del.path, original);
5806
+ }
5807
+ } else {
5808
+ this.dbLog("force delete", "[FORCE DELETE]", del.path);
5809
+ fs11.unlinkSync(del.path);
5810
+ }
5811
+ return "ok";
5812
+ } catch (e) {
5813
+ this.dbLog("restore failed", "[RESTORE ERROR]", e);
5814
+ return e;
5970
5815
  }
5971
- return "ok";
5972
- } catch (e) {
5973
- this.dbLog("restore failed", "[RESTORE ERROR]", e);
5974
- return e;
5975
5816
  }
5976
- }
5977
- list(dir, sql) {
5978
- try {
5979
- const absDir = this.resolvePath(dir);
5980
- this.dbLog("list", "[LIST]", absDir);
5981
- let entries;
5817
+ list(dir, sql) {
5982
5818
  try {
5983
- entries = fs12.readdirSync(absDir, { withFileTypes: true });
5984
- } catch {
5985
- return sql?.list ? { open: [], delete: [] } : [];
5986
- }
5987
- const result = { open: [], delete: [] };
5988
- const deleteSuffix = ".delete" + this.fileExt;
5989
- for (const entry of entries) {
5990
- if (!entry.isFile()) continue;
5991
- const fileName = entry.name;
5992
- const isDelete = fileName.endsWith(deleteSuffix);
5993
- const baseName = isDelete ? fileName.slice(0, -deleteSuffix.length) : fileName;
5994
- if (this.fileExt && !baseName.endsWith(this.fileExt)) continue;
5995
- const fullPath = path7.join(absDir, fileName);
5819
+ const absDir = this.resolvePath(dir);
5820
+ this.dbLog("list", "[LIST]", absDir);
5821
+ let entries;
5996
5822
  try {
5997
- const data = fs12.readFileSync(fullPath, "utf8");
5998
- const entryData = { data, file: baseName };
5999
- if (isDelete) {
6000
- if (sql?.list) result.delete.push(entryData);
6001
- } else {
6002
- result.open.push(entryData);
6003
- }
5823
+ entries = fs11.readdirSync(absDir, { withFileTypes: true });
6004
5824
  } catch {
5825
+ return sql?.list ? { open: [], delete: [] } : [];
6005
5826
  }
5827
+ const result = { open: [], delete: [] };
5828
+ const deleteSuffix = ".delete" + this.fileExt;
5829
+ for (const entry of entries) {
5830
+ if (!entry.isFile()) continue;
5831
+ const fileName = entry.name;
5832
+ const isDelete = fileName.endsWith(deleteSuffix);
5833
+ const baseName = isDelete ? fileName.slice(0, -deleteSuffix.length) : fileName;
5834
+ if (this.fileExt && !baseName.endsWith(this.fileExt)) continue;
5835
+ const fullPath = path6.join(absDir, fileName);
5836
+ try {
5837
+ const data = fs11.readFileSync(fullPath, "utf8");
5838
+ const entryData = { data, file: baseName };
5839
+ if (isDelete) {
5840
+ if (sql?.list) result.delete.push(entryData);
5841
+ } else {
5842
+ result.open.push(entryData);
5843
+ }
5844
+ } catch {
5845
+ }
5846
+ }
5847
+ return sql?.list ? result : result.open;
5848
+ } catch (e) {
5849
+ this.dbLog("list failed", "[LIST ERROR]", e);
5850
+ return e;
6006
5851
  }
6007
- return sql?.list ? result : result.open;
6008
- } catch (e) {
6009
- this.dbLog("list failed", "[LIST ERROR]", e);
6010
- return e;
6011
5852
  }
6012
- }
6013
- /** 从 where 首条条件中提取文件名(去掉 SQL 风格引号) */
6014
- getFileNameFromWhere(where) {
6015
- if (!where || !Array.isArray(where)) return "";
6016
- const first = where.find((item) => Array.isArray(item));
6017
- const raw = first?.[2];
6018
- if (typeof raw === "string") {
6019
- return raw.replace(/^'+|'+$/g, "");
5853
+ /** 从 where 首条条件中提取文件名(去掉 SQL 风格引号) */
5854
+ getFileNameFromWhere(where) {
5855
+ if (!where || !Array.isArray(where)) return "";
5856
+ const first = where.find((item) => Array.isArray(item));
5857
+ const raw = first?.[2];
5858
+ if (typeof raw === "string") {
5859
+ return raw.replace(/^'+|'+$/g, "");
5860
+ }
5861
+ return String(raw ?? "");
6020
5862
  }
6021
- return String(raw ?? "");
6022
- }
6023
- sqlDb(sql) {
6024
- return new Promise((resolve7, reject) => {
6025
- try {
6026
- let result;
6027
- const tableDir = this.tableFilePath(sql.table);
6028
- if (sql.type === "insert") {
6029
- const [file, data] = sql.value[0];
6030
- result = this.write(this.tableFilePath(sql.table, file), data, sql);
6031
- } else if (sql.type === "delete") {
6032
- const file = this.getFileNameFromWhere(sql.where);
6033
- result = this.del(this.tableFilePath(sql.table, file), sql);
6034
- } else if (sql.type === "re") {
6035
- const file = this.getFileNameFromWhere(sql.where);
6036
- result = this.re(this.tableFilePath(sql.table, file), sql);
6037
- } else if (sql.type === "update") {
6038
- const file = this.getFileNameFromWhere(sql.where);
6039
- const data = sql.set[0][1];
6040
- result = this.write(this.tableFilePath(sql.table, file), data, sql);
6041
- } else if (sql.type === "select") {
6042
- if (sql?.where?.length && sql?.as !== 2) {
5863
+ sqlDb(sql) {
5864
+ return new Promise((resolve6, reject) => {
5865
+ try {
5866
+ let result;
5867
+ const tableDir = this.tableFilePath(sql.table);
5868
+ if (sql.type === "insert") {
5869
+ const [file, data] = sql.value[0];
5870
+ result = this.write(this.tableFilePath(sql.table, file), data, sql);
5871
+ } else if (sql.type === "delete") {
6043
5872
  const file = this.getFileNameFromWhere(sql.where);
6044
- const readRes = this.read(this.tableFilePath(sql.table, file), sql);
6045
- const resObj = {};
6046
- if (readRes?.[0]?.file && !readRes.error) {
6047
- if (sql.as === 1) {
6048
- try {
6049
- const parsed = JSON.parse(readRes[0].data);
6050
- if (sql.field?.length) {
6051
- sql.field.forEach((f) => {
6052
- resObj[f] = parsed[f] ?? "";
6053
- });
6054
- } else {
6055
- Object.assign(resObj, parsed);
5873
+ result = this.del(this.tableFilePath(sql.table, file), sql);
5874
+ } else if (sql.type === "re") {
5875
+ const file = this.getFileNameFromWhere(sql.where);
5876
+ result = this.re(this.tableFilePath(sql.table, file), sql);
5877
+ } else if (sql.type === "update") {
5878
+ const file = this.getFileNameFromWhere(sql.where);
5879
+ const data = sql.set[0][1];
5880
+ result = this.write(this.tableFilePath(sql.table, file), data, sql);
5881
+ } else if (sql.type === "select") {
5882
+ if (sql?.where?.length && sql?.as !== 2) {
5883
+ const file = this.getFileNameFromWhere(sql.where);
5884
+ const readRes = this.read(this.tableFilePath(sql.table, file), sql);
5885
+ const resObj = {};
5886
+ if (readRes?.[0]?.file && !readRes.error) {
5887
+ if (sql.as === 1) {
5888
+ try {
5889
+ const parsed = JSON.parse(readRes[0].data);
5890
+ if (sql.field?.length) {
5891
+ sql.field.forEach((f) => {
5892
+ resObj[f] = parsed[f] ?? "";
5893
+ });
5894
+ } else {
5895
+ Object.assign(resObj, parsed);
5896
+ }
5897
+ } catch {
5898
+ }
5899
+ } else {
5900
+ const fields = sql.field;
5901
+ resObj[fields[0]] = readRes[0].file;
5902
+ if (fields[1]) {
5903
+ resObj[fields[1]] = readRes[0].data;
6056
5904
  }
6057
- } catch {
6058
- }
6059
- } else {
6060
- const fields = sql.field;
6061
- resObj[fields[0]] = readRes[0].file;
6062
- if (fields[1]) {
6063
- resObj[fields[1]] = readRes[0].data;
6064
5905
  }
6065
5906
  }
6066
- }
6067
- result = [resObj];
6068
- } else {
6069
- const listRes = this.list(tableDir, sql);
6070
- if (listRes && !listRes.error) {
6071
- if (sql?.list) {
6072
- const mapEntry = /* @__PURE__ */ __name((el) => {
6073
- const fields = sql.field;
6074
- const item = { [fields[0]]: el.file };
6075
- if (fields[1]) item[fields[1]] = el.data;
6076
- return item;
6077
- }, "mapEntry");
6078
- if (listRes.open) listRes.open = listRes.open.map(mapEntry);
6079
- if (listRes.delete) listRes.delete = listRes.delete.map(mapEntry);
6080
- result = listRes;
6081
- } else {
6082
- if (sql?.as === 2) {
6083
- let items = listRes;
6084
- if (sql.where?.length) {
6085
- const where = sql.where;
6086
- const whereType = typeof where[0] === "string" ? where[0] : "and";
6087
- items = items.filter((el) => {
6088
- let match = whereType === "and";
6089
- for (let i = 0; i < where.length; i++) {
6090
- if (typeof where[i] === "string") continue;
6091
- const cond = where[i];
6092
- if (!cond) continue;
6093
- const field = cond[0];
6094
- const searchVal = String(cond[2]).replace(/%/g, "");
6095
- let fieldVal = "";
6096
- try {
6097
- fieldVal = JSON.parse(el.data)[field] ?? "";
6098
- } catch {
5907
+ result = [resObj];
5908
+ } else {
5909
+ const listRes = this.list(tableDir, sql);
5910
+ if (listRes && !listRes.error) {
5911
+ if (sql?.list) {
5912
+ const mapEntry = /* @__PURE__ */ __name((el) => {
5913
+ const fields = sql.field;
5914
+ const item = { [fields[0]]: el.file };
5915
+ if (fields[1]) item[fields[1]] = el.data;
5916
+ return item;
5917
+ }, "mapEntry");
5918
+ if (listRes.open) listRes.open = listRes.open.map(mapEntry);
5919
+ if (listRes.delete) listRes.delete = listRes.delete.map(mapEntry);
5920
+ result = listRes;
5921
+ } else {
5922
+ if (sql?.as === 2) {
5923
+ let items = listRes;
5924
+ if (sql.where?.length) {
5925
+ const where = sql.where;
5926
+ const whereType = typeof where[0] === "string" ? where[0] : "and";
5927
+ items = items.filter((el) => {
5928
+ let match = whereType === "and";
5929
+ for (let i = 0; i < where.length; i++) {
5930
+ if (typeof where[i] === "string") continue;
5931
+ const cond = where[i];
5932
+ if (!cond) continue;
5933
+ const field = cond[0];
5934
+ const searchVal = String(cond[2]).replace(/%/g, "");
5935
+ let fieldVal = "";
5936
+ try {
5937
+ fieldVal = JSON.parse(el.data)[field] ?? "";
5938
+ } catch {
5939
+ }
5940
+ const condMatch = String(fieldVal).includes(searchVal);
5941
+ if (whereType === "and") {
5942
+ match = match && condMatch;
5943
+ if (!match) break;
5944
+ } else {
5945
+ match = match || condMatch;
5946
+ if (match) break;
5947
+ }
6099
5948
  }
6100
- const condMatch = String(fieldVal).includes(searchVal);
6101
- if (whereType === "and") {
6102
- match = match && condMatch;
6103
- if (!match) break;
5949
+ return match;
5950
+ });
5951
+ }
5952
+ items = items.map((el) => {
5953
+ const obj = {};
5954
+ try {
5955
+ const parsed = JSON.parse(el.data);
5956
+ if (sql.field?.length) {
5957
+ sql.field.forEach((f) => {
5958
+ obj[f] = parsed[f] ?? "";
5959
+ });
6104
5960
  } else {
6105
- match = match || condMatch;
6106
- if (match) break;
5961
+ Object.assign(obj, parsed);
6107
5962
  }
5963
+ } catch {
6108
5964
  }
6109
- return match;
5965
+ return obj;
6110
5966
  });
6111
- }
6112
- items = items.map((el) => {
6113
- const obj = {};
6114
- try {
6115
- const parsed = JSON.parse(el.data);
6116
- if (sql.field?.length) {
6117
- sql.field.forEach((f) => {
6118
- obj[f] = parsed[f] ?? "";
6119
- });
6120
- } else {
6121
- Object.assign(obj, parsed);
6122
- }
6123
- } catch {
5967
+ if (sql.order?.length) {
5968
+ const orderField = sql.order[0];
5969
+ const orderFieldName = typeof orderField === "string" ? orderField : orderField[0];
5970
+ const direction = typeof orderField === "string" ? "ASC" : String(orderField[1]).toUpperCase();
5971
+ items.sort((a, b) => {
5972
+ const valA = String(a[orderFieldName] ?? "");
5973
+ const valB = String(b[orderFieldName] ?? "");
5974
+ const cmp = valA.localeCompare(valB);
5975
+ return direction === "DESC" ? -cmp : cmp;
5976
+ });
6124
5977
  }
6125
- return obj;
6126
- });
6127
- if (sql.order?.length) {
6128
- const orderField = sql.order[0];
6129
- const orderFieldName = typeof orderField === "string" ? orderField : orderField[0];
6130
- const direction = typeof orderField === "string" ? "ASC" : String(orderField[1]).toUpperCase();
6131
- items.sort((a, b) => {
6132
- const valA = String(a[orderFieldName] ?? "");
6133
- const valB = String(b[orderFieldName] ?? "");
6134
- const cmp = valA.localeCompare(valB);
6135
- return direction === "DESC" ? -cmp : cmp;
6136
- });
5978
+ result = items;
5979
+ } else {
5980
+ const fields = sql.field;
5981
+ result = listRes.map((el) => ({
5982
+ [fields[0]]: el.file,
5983
+ ...fields[1] ? { [fields[1]]: el.data } : {}
5984
+ }));
6137
5985
  }
6138
- result = items;
6139
- } else {
6140
- const fields = sql.field;
6141
- result = listRes.map((el) => ({
6142
- [fields[0]]: el.file,
6143
- ...fields[1] ? { [fields[1]]: el.data } : {}
6144
- }));
6145
5986
  }
5987
+ } else {
5988
+ result = sql?.list ? { open: [], delete: [] } : [];
6146
5989
  }
6147
- } else {
6148
- result = sql?.list ? { open: [], delete: [] } : [];
6149
5990
  }
6150
5991
  }
5992
+ if (result !== void 0) {
5993
+ resolve6(result);
5994
+ } else {
5995
+ reject("");
5996
+ }
5997
+ } catch (e) {
5998
+ reject(e);
6151
5999
  }
6152
- if (result !== void 0) {
6153
- resolve7(result);
6154
- } else {
6155
- reject("");
6156
- }
6157
- } catch (e) {
6158
- reject(e);
6159
- }
6160
- });
6000
+ });
6001
+ }
6161
6002
  }
6162
- };
6163
- var Index_default4 = DbFile;
6003
+ return DbFile;
6004
+ }
6005
+ __name(dbDriver, "dbDriver");
6006
+ var Index_default2 = dbDriver;
6164
6007
 
6165
6008
  // src/sysserver/db/db.ts
6166
- var DRIVERS = {
6167
- mysql: Index_default2,
6168
- sqlite: Index_default3,
6169
- file: Index_default4
6170
- };
6171
- function initDb(type = "file", config = {}) {
6172
- const Driver = DRIVERS[type];
6173
- if (Driver) {
6174
- return new Driver(config);
6009
+ var BUILTIN_FILE = "file";
6010
+ function buildFileDriver() {
6011
+ return Index_default2({ IdbType: {}, Sql: Sql_default, DbCommon: common_exports });
6012
+ }
6013
+ __name(buildFileDriver, "buildFileDriver");
6014
+ function resolveDriver(type, dbDrivers) {
6015
+ if (type === BUILTIN_FILE) {
6016
+ return buildFileDriver();
6017
+ }
6018
+ const factory = dbDrivers[type];
6019
+ if (typeof factory !== "function") {
6020
+ return null;
6175
6021
  }
6176
- return {};
6022
+ return factory({ IdbType: {}, Sql: Sql_default, DbCommon: common_exports });
6177
6023
  }
6178
- __name(initDb, "initDb");
6024
+ __name(resolveDriver, "resolveDriver");
6025
+ var initDb = /* @__PURE__ */ __name((type = BUILTIN_FILE, config = {}, dbDrivers = {}) => {
6026
+ let Driver = resolveDriver(type, dbDrivers);
6027
+ if (!Driver) {
6028
+ if (type !== BUILTIN_FILE) {
6029
+ console.warn(
6030
+ `[initDb] \u672A\u6CE8\u518C\u9A71\u52A8 "${type}"\uFF08\u8BF7\u5728 sysserver($asai, { db: { ${type}: driver } }) \u6302\u8F7D\uFF09\uFF0C\u5DF2\u56DE\u9000 file \u5B58\u50A8`
6031
+ );
6032
+ }
6033
+ Driver = buildFileDriver();
6034
+ }
6035
+ return new Driver(config);
6036
+ }, "initDb");
6179
6037
  var db_default = initDb;
6180
6038
 
6181
6039
  // src/sysserver/index.ts
6182
- var sysserver_default = /* @__PURE__ */ __name(($asai) => {
6040
+ function sysserver($asai, opt = {}) {
6183
6041
  if (!$asai.$lib) $asai.$lib = {};
6042
+ $asai.$lib = { ...lib_default, ...$asai.$lib, ...opt || {} };
6184
6043
  $asai.dataserver = db_default;
6185
- $asai.$lib = { ...lib_default, ...$asai.$lib };
6186
- if (!$asai.asaifs) {
6187
- $asai.asaifs = $asai.$lib.AsFs;
6188
- }
6189
6044
  $asai.$lib.ErrorCode?.loadErrorCodes?.($asai);
6190
6045
  if (!$asai.$lib.api) $asai.$lib.api = {};
6191
6046
  $asai.$lib.api = { ...api_default, ...$asai.$lib.api };
6192
6047
  if (!$asai.$lib.ws) $asai.$lib.ws = {};
6193
6048
  $asai.$lib.ws = { ...ws_default, ...$asai.$lib.ws };
6194
- }, "default");
6049
+ }
6050
+ __name(sysserver, "sysserver");
6195
6051
 
6196
6052
  // src/config/validate.ts
6197
6053
  function normalizeHostConfig(cfg) {
@@ -6306,6 +6162,12 @@ function normalizeUserStoreConfig(cfg) {
6306
6162
  } else if (store.type === "sqlite") {
6307
6163
  if (!store.opt.database) store.opt.database = "./webdata/webdb/users/users.db";
6308
6164
  if (store.opt.create == null) store.opt.create = 1;
6165
+ } else if (store.type === "mysql") {
6166
+ if (!store.opt.host) store.opt.host = "127.0.0.1";
6167
+ if (!store.opt.user) store.opt.user = "root";
6168
+ if (store.opt.password == null) store.opt.password = "";
6169
+ if (!store.opt.database) store.opt.database = "asai";
6170
+ if (store.opt.create == null) store.opt.create = 1;
6309
6171
  }
6310
6172
  }
6311
6173
  __name(normalizeUserStoreConfig, "normalizeUserStoreConfig");
@@ -6348,6 +6210,30 @@ function normalizeDataStoresConfig(cfg) {
6348
6210
  if (!stores.user && cfg.userStore) {
6349
6211
  stores.user = { type: cfg.userStore.type || "file", opt: { ...cfg.userStore.opt || {} } };
6350
6212
  }
6213
+ if (!stores.user) {
6214
+ stores.user = {
6215
+ type: "file",
6216
+ opt: { dir: "webdata/webdb/users/", ext: ".json", create: 1 }
6217
+ };
6218
+ }
6219
+ if (stores.user && typeof stores.user === "object") {
6220
+ const userStore = stores.user;
6221
+ const userType = userStore.type || "file";
6222
+ if (!userStore.type) userStore.type = "file";
6223
+ if (!userStore.opt || typeof userStore.opt !== "object") userStore.opt = {};
6224
+ if (userType === "file") {
6225
+ if (!userStore.opt.dir) userStore.opt.dir = "webdata/webdb/users/";
6226
+ if (!userStore.opt.ext) userStore.opt.ext = ".json";
6227
+ } else if (userType === "sqlite") {
6228
+ if (!userStore.opt.database) userStore.opt.database = "./webdata/webdb/users/users.db";
6229
+ } else if (userType === "mysql") {
6230
+ if (!userStore.opt.host) userStore.opt.host = "127.0.0.1";
6231
+ if (!userStore.opt.user) userStore.opt.user = "root";
6232
+ if (userStore.opt.password == null) userStore.opt.password = "";
6233
+ if (!userStore.opt.database) userStore.opt.database = "asai";
6234
+ }
6235
+ if (userStore.opt.create == null) userStore.opt.create = 1;
6236
+ }
6351
6237
  if (!stores.log && cfg.logger?.store) {
6352
6238
  const s = cfg.logger.store;
6353
6239
  stores.log = {
@@ -6674,10 +6560,10 @@ function hostDefault(mod) {
6674
6560
  }
6675
6561
  __name(hostDefault, "hostDefault");
6676
6562
  var hostMain = hostDefault(src_default);
6677
- var initAsaiHost2 = {
6563
+ var initAsaiHostNodejs2 = {
6678
6564
  name: PLUGIN_NAME,
6679
6565
  fn: /* @__PURE__ */ __name(($asai, opt) => hostMain($asai, opt), "fn"),
6680
- sysserver: sysserver_default,
6566
+ sysserver,
6681
6567
  AsaiCommon: Index_default,
6682
6568
  AsaiUtils: utils_exports,
6683
6569
  normalizeHostConfig,
@@ -6714,8 +6600,8 @@ var initAsaiHost2 = {
6714
6600
  createTransportDataHandler,
6715
6601
  wireTransportConnectionLogs
6716
6602
  };
6717
- var PLUGIN_VERSION = true ? "0.0.1" : "0.0.1";
6718
- var index_default = initAsaiHost2;
6603
+ var PLUGIN_VERSION = true ? "0.0.2" : "0.0.1";
6604
+ var index_default = initAsaiHostNodejs2;
6719
6605
  export {
6720
6606
  Index_default as AsaiCommon,
6721
6607
  utils_exports as AsaiUtils,
@@ -6730,7 +6616,7 @@ export {
6730
6616
  getTransportBlock,
6731
6617
  getTransportDefault,
6732
6618
  hasTransportDefault,
6733
- initAsaiHost2 as initAsaiHost,
6619
+ initAsaiHostNodejs2 as initAsaiHostNodejs,
6734
6620
  initHostserverApi,
6735
6621
  initHostserverWs,
6736
6622
  initTransportBridge,
@@ -6751,7 +6637,7 @@ export {
6751
6637
  stripForbiddenSecretsFromHostConfig,
6752
6638
  syncApiRouteKeys,
6753
6639
  syncWsRouteKeys,
6754
- sysserver_default as sysserver,
6640
+ sysserver,
6755
6641
  validateAsaiSecrets,
6756
6642
  validateHostConfig,
6757
6643
  validateHttpAuth,