asaihost-nodejs 0.0.1 → 0.0.3

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 +1304 -1447
  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 +921 -1064
  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
  };
@@ -1795,12 +1847,6 @@ __name(setupHttpConnectionMonitor, "setupHttpConnectionMonitor");
1795
1847
  import { execSync } from "child_process";
1796
1848
  import process2 from "process";
1797
1849
  var OWN_PID = process2.pid;
1798
- function sleepMs(ms) {
1799
- const deadline = Date.now() + ms;
1800
- while (Date.now() < deadline) {
1801
- }
1802
- }
1803
- __name(sleepMs, "sleepMs");
1804
1850
  function findListeningPids(port) {
1805
1851
  if (!Number.isInteger(port) || port < 1 || port > 65535) return [];
1806
1852
  try {
@@ -1836,32 +1882,20 @@ function findListeningPids(port) {
1836
1882
  }
1837
1883
  }
1838
1884
  __name(findListeningPids, "findListeningPids");
1839
- function killPid(pid) {
1840
- if (!pid || pid === OWN_PID || pid <= 4) return false;
1841
- try {
1842
- if (process2.platform === "win32") {
1843
- execSync(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
1844
- } else {
1845
- execSync(`kill -9 ${pid}`, { stdio: "ignore" });
1846
- }
1847
- return true;
1848
- } catch {
1849
- return false;
1850
- }
1885
+ function formatOccupants(port, exclude) {
1886
+ const occupants = findListeningPids(port).filter((p) => p !== exclude && p !== OWN_PID);
1887
+ return [...new Set(occupants)].join(", ");
1851
1888
  }
1852
- __name(killPid, "killPid");
1889
+ __name(formatOccupants, "formatOccupants");
1853
1890
  function releasePort(port, opts) {
1854
1891
  const exclude = opts?.excludePid ?? OWN_PID;
1855
- const occupants = findListeningPids(port).filter((p) => p !== exclude && p !== OWN_PID);
1856
- if (!occupants.length) return true;
1892
+ const occupants = formatOccupants(port, exclude);
1893
+ if (!occupants) return true;
1857
1894
  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(", ")}`);
1859
- for (const pid of occupants) {
1860
- killPid(pid);
1861
- }
1862
- sleepMs(500);
1863
- const remaining = findListeningPids(port).filter((p) => p !== exclude && p !== OWN_PID);
1864
- return remaining.length === 0;
1895
+ console.warn(
1896
+ `[AsaiHostNodejs] ${label}\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF08PID: ${occupants}\uFF09\uFF0C\u8BF7\u624B\u52A8\u7ED3\u675F\u5360\u7528\u8FDB\u7A0B\u540E\u91CD\u8BD5`
1897
+ );
1898
+ return false;
1865
1899
  }
1866
1900
  __name(releasePort, "releasePort");
1867
1901
  function collectStartupPorts($asai) {
@@ -2014,7 +2048,6 @@ function startHTTPServer($asai, hostdir) {
2014
2048
  ${err.stack}` : err);
2015
2049
  }
2016
2050
  });
2017
- releasePort(hostcfg.port, { excludePid: process.pid, label: hostdir });
2018
2051
  if ($asai.hostconfig.ip === "asai" || !hostcfg.ip && !$asai.hostconfig.ip) {
2019
2052
  server.listen(hostcfg.port, () => {
2020
2053
  console.log(`[${hostdir}] Start At ${getNowTime()}.`);
@@ -2066,19 +2099,13 @@ function asaiGuard($asai, failedHostdir = "asai") {
2066
2099
  const sameProcessOwner = findSameProcessPortOwner($asai, port, failedHostdir);
2067
2100
  if (sameProcessOwner) {
2068
2101
  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`
2102
+ `[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
2103
  );
2071
2104
  return;
2072
2105
  }
2073
- const released = releasePort(port, { excludePid: process.pid, label: failedHostdir });
2106
+ releasePort(port, { excludePid: process.pid, label: failedHostdir });
2074
2107
  $asai.hostconfig.asaiport = conflictPorts.filter((p) => p !== port);
2075
- if (released) {
2076
- console.warn(`[AsaiHost] \u7AEF\u53E3 ${port} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${failedHostdir}]`);
2077
- scheduleHttpRetry($asai, failedHostdir);
2078
- return;
2079
- }
2080
- console.error(`[AsaiHost] \u7AEF\u53E3 ${port} \u65E0\u6CD5\u91CA\u653E\uFF0C\u542F\u52A8\u5931\u8D25\uFF08netstat -ano | findstr :${port}\uFF09`);
2081
- process.exit(1);
2108
+ scheduleHttpRetry($asai, failedHostdir);
2082
2109
  }
2083
2110
  __name(asaiGuard, "asaiGuard");
2084
2111
 
@@ -2179,21 +2206,17 @@ function startProxyServer($asai, proxyhostdir) {
2179
2206
  if (err.code === "EADDRINUSE") {
2180
2207
  $asai.$log("PROXY", `\x1B[41mPort ${proxycfg.proxyport} is already start!\x1B[0m`);
2181
2208
  delete $asai.serversproxy?.[proxyhostdir];
2182
- const released = releasePort(proxycfg.proxyport, {
2209
+ releasePort(proxycfg.proxyport, {
2183
2210
  excludePid: process.pid,
2184
2211
  label: proxyhostdir
2185
2212
  });
2186
- if (released) {
2187
- console.warn(`[AsaiHost] \u4EE3\u7406\u7AEF\u53E3 ${proxycfg.proxyport} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${proxyhostdir}]`);
2188
- setTimeout(() => startProxyServer($asai, proxyhostdir), $asai.hostconfig.tm?.d || 2e3);
2189
- }
2213
+ setTimeout(() => startProxyServer($asai, proxyhostdir), $asai.hostconfig.tm?.d || 2e3);
2190
2214
  } else {
2191
2215
  $asai.$log("PROXY", "Proxy server error:", err);
2192
2216
  }
2193
2217
  });
2194
2218
  if (!$asai.serversproxy) $asai.serversproxy = {};
2195
2219
  $asai.serversproxy[proxyhostdir] = proxyServer;
2196
- releasePort(proxycfg.proxyport, { excludePid: process.pid, label: proxyhostdir });
2197
2220
  const listenIp = getHostIP(proxycfg.proxyip, $asai);
2198
2221
  const listenCallback = /* @__PURE__ */ __name(() => {
2199
2222
  console.log(`[${proxyhostdir}] Start At ${getNowTime()}.`);
@@ -2223,8 +2246,8 @@ function src_default($asai, opt) {
2223
2246
  workHandlers.set("apiWork", opt.apiWork);
2224
2247
  workHandlers.set("wsWork", opt.wsWork);
2225
2248
  workHandlers.set("jsonpWork", opt.jsonpWork);
2226
- function StartAsaiHost() {
2227
- initAsaiHost($asai, opt);
2249
+ function StartAsaiHostNodejs() {
2250
+ initAsaiHostNodejs($asai, opt);
2228
2251
  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
2252
  releaseConfiguredPorts($asai);
2230
2253
  if ($asai.hostconfig?.hosts) {
@@ -2246,8 +2269,8 @@ function src_default($asai, opt) {
2246
2269
  });
2247
2270
  }
2248
2271
  }
2249
- __name(StartAsaiHost, "StartAsaiHost");
2250
- return { StartAsaiHost, asaiWs: ws };
2272
+ __name(StartAsaiHostNodejs, "StartAsaiHostNodejs");
2273
+ return { StartAsaiHostNodejs, asaiWs: ws };
2251
2274
  }
2252
2275
  __name(src_default, "default");
2253
2276
 
@@ -2319,7 +2342,7 @@ async function attachServerUserLevel(req, $asai) {
2319
2342
  if (!Array.isArray(userinfo) || !userinfo[0]) return;
2320
2343
  const us = String(userinfo[0]);
2321
2344
  try {
2322
- const { createUserStore: createUserStore2 } = await import("./user-store-J7LWEXSW.es.js");
2345
+ const { createUserStore: createUserStore2 } = await import("./user-store-UC5URX3C.es.js");
2323
2346
  const user = await createUserStore2($asai).findByUsername(us);
2324
2347
  if (user && user.status === "active") {
2325
2348
  req._serverUser = { us: user.us, lv: user.lv, status: user.status };
@@ -2350,11 +2373,11 @@ function getSecurityConfig($asai) {
2350
2373
  return $asai?.hostconfig?.security || {};
2351
2374
  }
2352
2375
  __name(getSecurityConfig, "getSecurityConfig");
2353
- function pathMatchesPattern(path8, pattern) {
2376
+ function pathMatchesPattern(path7, pattern) {
2354
2377
  if (pattern.endsWith("*")) {
2355
- return path8.startsWith(pattern.slice(0, -1));
2378
+ return path7.startsWith(pattern.slice(0, -1));
2356
2379
  }
2357
- return path8 === pattern;
2380
+ return path7 === pattern;
2358
2381
  }
2359
2382
  __name(pathMatchesPattern, "pathMatchesPattern");
2360
2383
  function isAuthSkipped($asai, pathname) {
@@ -2502,6 +2525,54 @@ function isApiCorsAllowed(origin, hostcfg, hostHeader) {
2502
2525
  __name(isApiCorsAllowed, "isApiCorsAllowed");
2503
2526
 
2504
2527
  // src/common/server/Api.ts
2528
+ function readRequestBody(req, maxBody) {
2529
+ return new Promise((resolve6, reject) => {
2530
+ const chunks = [];
2531
+ let received = 0;
2532
+ let settled = false;
2533
+ const finish = /* @__PURE__ */ __name((err, data) => {
2534
+ if (settled) return;
2535
+ settled = true;
2536
+ if (err) reject(err);
2537
+ else resolve6(data ?? "");
2538
+ }, "finish");
2539
+ req.on("data", (chunk) => {
2540
+ if (settled) return;
2541
+ received += chunk.length;
2542
+ if (received > maxBody) {
2543
+ req.destroy();
2544
+ finish(new Error("PAYLOAD_TOO_LARGE"));
2545
+ return;
2546
+ }
2547
+ chunks.push(chunk);
2548
+ });
2549
+ req.on("end", () => finish(null, Buffer.concat(chunks).toString("utf8")));
2550
+ req.on("error", (err) => finish(err));
2551
+ });
2552
+ }
2553
+ __name(readRequestBody, "readRequestBody");
2554
+ function processPostPayload($asai, req, postdata, pm) {
2555
+ let isAes = false;
2556
+ let out = postdata;
2557
+ if ($asai.$lib.aesfns && out?.startsWith?.('{"aes":')) {
2558
+ isAes = true;
2559
+ const aesData = JSON.parse(out);
2560
+ out = $asai.$lib.aesfns.reqAES({ data: aesData });
2561
+ if (aesData?.aes === 2 && typeof out === "string") {
2562
+ try {
2563
+ out = JSON.parse(out);
2564
+ } catch {
2565
+ }
2566
+ }
2567
+ }
2568
+ const plainJson = typeof out === "string" && out.trimStart().startsWith("{");
2569
+ if (req?.Userinfo?.[2] && !plainJson) {
2570
+ pm = req.Userinfo[2];
2571
+ out = $asai.$lib.AsCode.asdecode(out, pm);
2572
+ }
2573
+ return { postdata: out, pm, isAes };
2574
+ }
2575
+ __name(processPostPayload, "processPostPayload");
2505
2576
  var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
2506
2577
  opt?.fn?.($asai);
2507
2578
  function getHeader(req, key) {
@@ -2581,8 +2652,24 @@ var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
2581
2652
  const releaseSlot = /* @__PURE__ */ __name(() => releaseApiSlot(hostdir, clientIp), "releaseSlot");
2582
2653
  res.once("finish", releaseSlot);
2583
2654
  res.once("close", releaseSlot);
2655
+ const maxBody = $asai.hostconfig?.maxbody || 100 * 1024 * 1024;
2656
+ const isTextPost = req.method === "POST" && req.url.indexOf("binary") === -1;
2657
+ const bodyPromise = isTextPost ? readRequestBody(req, maxBody) : null;
2584
2658
  void (async () => {
2585
- const authResult = await validateHttpAuthAsync(req, $asai, pathname);
2659
+ let authResult;
2660
+ let rawBody = "";
2661
+ try {
2662
+ [authResult, rawBody] = await Promise.all([
2663
+ validateHttpAuthAsync(req, $asai, pathname),
2664
+ bodyPromise ?? Promise.resolve("")
2665
+ ]);
2666
+ } catch (err) {
2667
+ releaseSlot();
2668
+ if (err?.message === "PAYLOAD_TOO_LARGE") {
2669
+ return sendError(res, 413, "Payload Too Large");
2670
+ }
2671
+ return sendError(res, 400, err?.message || "Bad Request");
2672
+ }
2586
2673
  if (!authResult.ok) {
2587
2674
  releaseSlot();
2588
2675
  return sendAuthError(res, authResult);
@@ -2596,52 +2683,25 @@ var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
2596
2683
  let qrdata;
2597
2684
  let postdata;
2598
2685
  if (req.url.indexOf("binary") === -1) {
2599
- postdata = "";
2600
2686
  qrdata = query;
2601
2687
  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);
2688
+ try {
2689
+ const processed = processPostPayload($asai, req, rawBody, pm);
2690
+ postdata = processed.postdata;
2691
+ pm = processed.pm;
2692
+ isAes = processed.isAes;
2693
+ } catch {
2694
+ return sendError(res, 400, "AES decrypt failed");
2695
+ }
2696
+ if (apiFn?.post && typeof apiFn?.post === "function") {
2697
+ const optTmp = { qrdata, data: postdata, pm };
2698
+ if (isAes || $asai.$lib.aesfns?.allaes) {
2699
+ optTmp.resAES = $asai.$lib.aesfns?.resAES;
2643
2700
  }
2644
- });
2701
+ apiFn.post(req, res, hostdir, optTmp);
2702
+ } else {
2703
+ res.end(typeof postdata === "string" ? postdata : JSON.stringify(postdata ?? ""));
2704
+ }
2645
2705
  } else if (req.method === "GET") {
2646
2706
  if (apiFn?.get && typeof apiFn?.get === "function") {
2647
2707
  const optTmp = { qrdata, data: qrdata, pm };
@@ -2845,7 +2905,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2845
2905
  }
2846
2906
  __name(getKeyByData, "getKeyByData");
2847
2907
  function clientwsInit() {
2848
- return new Promise((resolve7, reject) => {
2908
+ return new Promise((resolve6, reject) => {
2849
2909
  $asai.clientws[wsname].socket = new $asai.asaiWs(
2850
2910
  $asai.command.commandconfig.clientws[wsname]
2851
2911
  );
@@ -2875,7 +2935,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2875
2935
  `${wsname}=${$asai.command.commandconfig.clientws[wsname]} is open.`
2876
2936
  );
2877
2937
  $asai.clientws[wsname].open = 1;
2878
- resolve7();
2938
+ resolve6();
2879
2939
  };
2880
2940
  });
2881
2941
  }
@@ -2905,7 +2965,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2905
2965
  }
2906
2966
  __name(clientwsOnmessage, "clientwsOnmessage");
2907
2967
  function clientwsApi(messageData) {
2908
- return new Promise((resolve7, reject) => {
2968
+ return new Promise((resolve6, reject) => {
2909
2969
  if ($asai.clientws[wsname].open) {
2910
2970
  try {
2911
2971
  const taskOpt = {};
@@ -2917,7 +2977,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
2917
2977
  getKeyByData(messageData),
2918
2978
  {
2919
2979
  callback: /* @__PURE__ */ __name((res) => {
2920
- resolve7(res);
2980
+ resolve6(res);
2921
2981
  }, "callback"),
2922
2982
  ...messageData,
2923
2983
  ...taskOpt
@@ -3434,7 +3494,7 @@ var unifiedlog_default = /* @__PURE__ */ __name(($asai) => {
3434
3494
  if (url.startsWith("/api/asailog/store/formats/")) {
3435
3495
  if (!await ensureLogReadAccess(req, $asai, getPermissionThreshold($asai, "logRead"))) return sendFail(res, "Forbidden", opt);
3436
3496
  try {
3437
- const { getLogFormatTaxonomy } = await import("./log-formats-I5E32H5F.es.js");
3497
+ const { getLogFormatTaxonomy } = await import("./log-formats-WW3TDUAK.es.js");
3438
3498
  return sendSuccess(res, getLogFormatTaxonomy($asai), opt);
3439
3499
  } catch (err) {
3440
3500
  return sendFail(res, err?.message || err, opt);
@@ -3798,7 +3858,7 @@ __name(buildSessionRecord, "buildSessionRecord");
3798
3858
  var API_ADMIN_PREFIXES = ["/api/user/login/admin/"];
3799
3859
  function getMinLevelForApiPath(pathname, $asai) {
3800
3860
  if (API_ADMIN_PREFIXES.some((p) => pathname.startsWith(p))) {
3801
- if (pathname.includes("/admin/update") || pathname.includes("/admin/kick") || pathname.includes("/admin/delete")) {
3861
+ if (pathname.includes("/admin/update") || pathname.includes("/admin/kick") || pathname.includes("/admin/delete") || pathname.includes("/admin/resetpass")) {
3802
3862
  return $asai ? getPermissionThreshold($asai, "adminWrite") : USER_LV.DEVELOPER;
3803
3863
  }
3804
3864
  return $asai ? getPermissionThreshold($asai, "adminRead") : USER_LV.ADMIN;
@@ -3811,19 +3871,98 @@ function hasLevel(userLevel, required) {
3811
3871
  }
3812
3872
  __name(hasLevel, "hasLevel");
3813
3873
 
3814
- // src/sysserver/api/login/index.ts
3815
- function parseBody2(opt) {
3816
- if (!opt?.data) return {};
3817
- if (typeof opt.data === "string") {
3874
+ // src/sysserver/api/login/service/request-body.ts
3875
+ function isAesEnvelope(body) {
3876
+ 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;
3877
+ }
3878
+ __name(isAesEnvelope, "isAesEnvelope");
3879
+ function unwrapJsonString(value) {
3880
+ if (typeof value !== "string") return value;
3881
+ const text = value.trim();
3882
+ if (!text.startsWith("{") && !text.startsWith("[")) return value;
3883
+ try {
3884
+ return JSON.parse(text);
3885
+ } catch {
3886
+ return value;
3887
+ }
3888
+ }
3889
+ __name(unwrapJsonString, "unwrapJsonString");
3890
+ function parseAuthBody(opt) {
3891
+ let raw = opt?.data;
3892
+ if (raw == null || raw === "") return {};
3893
+ if (typeof raw === "string") {
3894
+ const text = raw.trim();
3895
+ if (!text) return {};
3818
3896
  try {
3819
- return JSON.parse(opt.data);
3897
+ raw = JSON.parse(text);
3820
3898
  } catch {
3821
- return {};
3899
+ if (text.includes("=") && !text.startsWith("{") && !text.startsWith("[")) {
3900
+ raw = Object.fromEntries(new URLSearchParams(text));
3901
+ } else {
3902
+ return {};
3903
+ }
3822
3904
  }
3905
+ raw = unwrapJsonString(raw);
3823
3906
  }
3824
- return typeof opt.data === "object" ? opt.data : {};
3907
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
3908
+ const body = raw;
3909
+ if (isAesEnvelope(body)) return {};
3910
+ return body;
3911
+ }
3912
+ __name(parseAuthBody, "parseAuthBody");
3913
+ function pickAuthField(body, ...keys) {
3914
+ for (const key of keys) {
3915
+ const val = body[key];
3916
+ if (val != null && String(val).length > 0) return String(val);
3917
+ }
3918
+ return "";
3825
3919
  }
3826
- __name(parseBody2, "parseBody");
3920
+ __name(pickAuthField, "pickAuthField");
3921
+
3922
+ // src/sysserver/api/login/service/online-sessions.ts
3923
+ function resolveOnlineSessions($asai, hostdir, allSessions) {
3924
+ const onlineUsers = new Set(listOnlineWsUsers($asai, hostdir));
3925
+ if (!onlineUsers.size) return [];
3926
+ const byUser = /* @__PURE__ */ new Map();
3927
+ for (const s of allSessions) {
3928
+ if (!onlineUsers.has(s.us)) continue;
3929
+ const prev = byUser.get(s.us);
3930
+ const sTime = s.lastActivityAt ?? s.createdAt;
3931
+ const prevTime = prev ? prev.lastActivityAt ?? prev.createdAt : 0;
3932
+ if (!prev || sTime > prevTime) byUser.set(s.us, s);
3933
+ }
3934
+ return [...byUser.values()].sort((a, b) => b.createdAt - a.createdAt);
3935
+ }
3936
+ __name(resolveOnlineSessions, "resolveOnlineSessions");
3937
+ function isSessionUserOnline($asai, us, hostdir) {
3938
+ if (!us) return false;
3939
+ return isUserOnlineOnWs($asai, us, hostdir);
3940
+ }
3941
+ __name(isSessionUserOnline, "isSessionUserOnline");
3942
+ async function countOnlineSessions($asai, hostdir, store) {
3943
+ const all = await store.getSessions();
3944
+ return resolveOnlineSessions($asai, hostdir, all).length;
3945
+ }
3946
+ __name(countOnlineSessions, "countOnlineSessions");
3947
+ async function countOnlineSessionsByUser($asai, hostdir, store, us) {
3948
+ const all = await store.getSessions();
3949
+ return resolveOnlineSessions($asai, hostdir, all).filter((s) => s.us === us).length;
3950
+ }
3951
+ __name(countOnlineSessionsByUser, "countOnlineSessionsByUser");
3952
+ async function purgeOfflineSessions($asai, hostdir, store) {
3953
+ const onlineUsers = new Set(listOnlineWsUsers($asai, hostdir));
3954
+ const all = await store.getSessions();
3955
+ let purged = 0;
3956
+ for (const s of all) {
3957
+ if (onlineUsers.has(s.us)) continue;
3958
+ await store.removeSession(s.id);
3959
+ purged += 1;
3960
+ }
3961
+ return purged;
3962
+ }
3963
+ __name(purgeOfflineSessions, "purgeOfflineSessions");
3964
+
3965
+ // src/sysserver/api/login/index.ts
3827
3966
  function getSecurity($asai) {
3828
3967
  return $asai?.hostconfig?.security || {};
3829
3968
  }
@@ -3833,16 +3972,16 @@ function getQuota2($asai) {
3833
3972
  return sec.quota || {};
3834
3973
  }
3835
3974
  __name(getQuota2, "getQuota");
3836
- async function canLogin($asai, us) {
3975
+ async function canLogin($asai, us, hostdir) {
3837
3976
  const quota = getQuota2($asai);
3838
3977
  if (!quota.enabled) return { ok: true };
3839
3978
  const store = createUserStore($asai);
3840
- const total = await store.countSessions();
3979
+ const total = await countOnlineSessions($asai, hostdir, store);
3841
3980
  const maxLogin = quota.maxLoginSessions ?? $asai?.hostconfig?.hosts?.default?.maxonline ?? 100;
3842
3981
  if (total >= maxLogin) {
3843
3982
  return { ok: false, message: "LOGIN_QUOTA_FULL" };
3844
3983
  }
3845
- const perUser = await store.countSessionsByUser(us);
3984
+ const perUser = await countOnlineSessionsByUser($asai, hostdir, store, us);
3846
3985
  const maxPerUser = quota.maxSessionsPerUser ?? 3;
3847
3986
  if (perUser >= maxPerUser) {
3848
3987
  return { ok: false, message: "USER_SESSION_LIMIT" };
@@ -3882,13 +4021,13 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3882
4021
  async function post(req, res, hostdir, opt) {
3883
4022
  const url = req.url || "";
3884
4023
  const ip = getClientIp(req);
3885
- const body = parseBody2(opt);
4024
+ const body = parseAuthBody(opt);
3886
4025
  if (url.startsWith("/api/user/login/changepass/")) {
3887
4026
  await ensureReady($asai);
3888
4027
  try {
3889
- const us = sanitizeString(body.us, 64);
3890
- const pass = sanitizeString(body.pass, 128);
3891
- const newPass = sanitizeString(body.newPass, 128);
4028
+ const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
4029
+ const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
4030
+ const newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
3892
4031
  if (!us || !pass || !newPass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
3893
4032
  const user = await store.findByUsername(us);
3894
4033
  if (!user || !verifyPassword(pass, user.passHash, user.passSalt)) {
@@ -3922,9 +4061,11 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3922
4061
  return sendErr(res, "AUTH_REG_DISABLED", void 0, opt);
3923
4062
  }
3924
4063
  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;
4064
+ const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
4065
+ const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
4066
+ const emailRaw = pickAuthField(body, "email");
4067
+ const email = emailRaw ? sanitizeString(emailRaw, 128) : void 0;
4068
+ if (!us || !pass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
3928
4069
  const policyErr = validatePasswordChange(pass, $asai);
3929
4070
  if (policyErr === "PASSWORD_TOO_SHORT") {
3930
4071
  return sendErr(res, policyErr, [String(getPasswordPolicy($asai).minLength)], opt);
@@ -3953,8 +4094,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3953
4094
  if (url.startsWith("/api/user/login/auth/")) {
3954
4095
  await ensureReady($asai);
3955
4096
  try {
3956
- const us = sanitizeString(body.us, 64);
3957
- const pass = sanitizeString(body.pass, 128);
4097
+ const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
4098
+ const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
3958
4099
  if (!us || !pass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
3959
4100
  const user = await store.findByUsername(us);
3960
4101
  if (!user || !verifyPassword(pass, user.passHash, user.passSalt)) {
@@ -3983,17 +4124,9 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
3983
4124
  const mustChange = !!user.mustChangePassword || isPasswordExpired(user.passwordChangedAt, policy.maxAgeDays);
3984
4125
  const hostcfg = $asai.getHost?.(hostdir) || {};
3985
4126
  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);
4127
+ kickUserWs($asai, us, hostdir);
3995
4128
  }
3996
- const quotaCheck = await canLogin($asai, us);
4129
+ const quotaCheck = await canLogin($asai, us, hostdir);
3997
4130
  if (!quotaCheck.ok) {
3998
4131
  logSecurityEvent($asai, { type: "RATE_LIMIT", path: url, method: "POST", user: us, ip, detail: quotaCheck.message });
3999
4132
  return sendErr(res, quotaCheck.message || "LOGIN_QUOTA_FULL", void 0, opt);
@@ -4037,12 +4170,17 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4037
4170
  if (!sessionId) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
4038
4171
  const sessions = await store.getSessions();
4039
4172
  const target = sessions.find((s) => s.id === sessionId);
4173
+ if (!target) return sendErr(res, "AUTH_SESSION_NOT_FOUND", void 0, opt);
4174
+ if (!isSessionUserOnline($asai, target.us, hostdir)) {
4175
+ return sendErr(res, "SESSION_NOT_ONLINE", void 0, opt);
4176
+ }
4177
+ kickUserWs($asai, target.us, hostdir);
4040
4178
  await store.removeSession(sessionId);
4041
4179
  const operator = String(req?.Userinfo?.[0] || "");
4042
4180
  await logSessionTerminate($asai, {
4043
4181
  operator,
4044
4182
  sessionId,
4045
- targetUs: target?.us,
4183
+ targetUs: target.us,
4046
4184
  ip
4047
4185
  });
4048
4186
  return sendSuccess(res, { ok: true }, opt);
@@ -4074,10 +4212,16 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4074
4212
  const levels = Array.isArray(body.levels) ? body.levels : void 0;
4075
4213
  const thresholds = body.thresholds && typeof body.thresholds === "object" ? body.thresholds : void 0;
4076
4214
  const routeRights = Array.isArray(body.routeRights) ? body.routeRights : void 0;
4215
+ const defaultResetPassword = typeof body.defaultResetPassword === "string" ? body.defaultResetPassword : void 0;
4077
4216
  const operator = String(req?.Userinfo?.[0] || "");
4078
4217
  const cfg = await saveUserLevelConfig(
4079
4218
  $asai,
4080
- { levels, thresholds, routeRights },
4219
+ {
4220
+ levels,
4221
+ thresholds,
4222
+ routeRights,
4223
+ defaultResetPassword
4224
+ },
4081
4225
  operator
4082
4226
  );
4083
4227
  await logSecurityConfigIntervention($asai, {
@@ -4091,6 +4235,57 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4091
4235
  return sendFail(res, err?.message || err, opt);
4092
4236
  }
4093
4237
  }
4238
+ if (url.startsWith("/api/user/login/admin/resetpass/")) {
4239
+ await ensureReady($asai);
4240
+ if (!await checkAdmin(req, url, getPermissionThreshold($asai, "adminWrite"))) {
4241
+ logSecurityEvent($asai, { type: "ACCESS_DENIED", path: url, method: "POST", ip });
4242
+ return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
4243
+ }
4244
+ try {
4245
+ const targetUs = sanitizeString(body.us, 64);
4246
+ if (!targetUs) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4247
+ let newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
4248
+ if (!newPass) newPass = getDefaultResetPassword($asai);
4249
+ const policy = getPasswordPolicy($asai);
4250
+ const policyErr = validatePasswordWithPolicy(newPass, policy);
4251
+ if (policyErr === "PASSWORD_TOO_SHORT") {
4252
+ return sendErr(res, policyErr, [String(policy.minLength)], opt);
4253
+ }
4254
+ if (policyErr) return sendErr(res, policyErr, void 0, opt);
4255
+ const user = await store.findByUsername(targetUs);
4256
+ if (!user) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4257
+ const { passHash, passSalt } = hashPassword(newPass);
4258
+ const updated = await store.updateUser(targetUs, {
4259
+ passHash,
4260
+ passSalt,
4261
+ mustChangePassword: 1,
4262
+ passwordChangedAt: Date.now(),
4263
+ failCount: 0,
4264
+ status: "active",
4265
+ lockedUntil: void 0,
4266
+ passHistory: "[]"
4267
+ });
4268
+ if (!updated) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4269
+ const operator = String(req?.Userinfo?.[0] || "");
4270
+ await logUserRbacChange($asai, {
4271
+ operator,
4272
+ targetUs,
4273
+ patch: { resetPassword: true, mustChangePassword: 1 },
4274
+ ip
4275
+ });
4276
+ logSecurityEvent($asai, {
4277
+ type: "AUTH_FAIL",
4278
+ path: url,
4279
+ method: "POST",
4280
+ user: targetUs,
4281
+ ip,
4282
+ detail: "admin_password_reset"
4283
+ });
4284
+ return sendSuccess(res, { user: publicUser($asai, updated) }, opt);
4285
+ } catch (err) {
4286
+ return sendFail(res, err?.message || err, opt);
4287
+ }
4288
+ }
4094
4289
  if (url.startsWith("/api/user/login/admin/update/")) {
4095
4290
  await ensureReady($asai);
4096
4291
  if (!await checkAdmin(req, url)) {
@@ -4136,11 +4331,12 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4136
4331
  try {
4137
4332
  const user = await store.findByUsername(String(us));
4138
4333
  if (!user) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
4139
- if (body.newPass) {
4140
- const newPass = sanitizeString(body.newPass, 128);
4334
+ if (pickAuthField(body, "newPass", "newPassword", "newpass")) {
4335
+ const newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
4141
4336
  const policy = getPasswordPolicy($asai);
4142
- if (body.pass) {
4143
- if (!verifyPassword(String(body.pass), user.passHash, user.passSalt)) {
4337
+ const oldPass = pickAuthField(body, "pass", "password", "pwd");
4338
+ if (oldPass) {
4339
+ if (!verifyPassword(oldPass, user.passHash, user.passSalt)) {
4144
4340
  return sendErr(res, "AUTH_INVALID_PASSWORD", void 0, opt);
4145
4341
  }
4146
4342
  } else if (!user.mustChangePassword) {
@@ -4152,7 +4348,7 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4152
4348
  }
4153
4349
  if (policyErr) return sendErr(res, policyErr, void 0, opt);
4154
4350
  const { passHash, passSalt } = hashPassword(newPass);
4155
- const passHistory = nextPassHistory(String(body.pass || newPass), user.passHistory, policy.historyCount);
4351
+ const passHistory = nextPassHistory(oldPass || newPass, user.passHistory, policy.historyCount);
4156
4352
  await store.updateUser(String(us), {
4157
4353
  passHash,
4158
4354
  passSalt,
@@ -4174,6 +4370,7 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4174
4370
  const ip = getClientIp(req);
4175
4371
  if (url.startsWith("/api/user/login/config/")) {
4176
4372
  const lang = String(req?.headers?.["accept-language"] || opt?.headers?.["accept-language"] || "zh-CN").split(",")[0];
4373
+ await loadUserLevelConfig($asai);
4177
4374
  return sendSuccess(res, getPublicSecurityConfig($asai, lang), opt);
4178
4375
  }
4179
4376
  if (url.startsWith("/api/user/login/admin/levels/")) {
@@ -4187,7 +4384,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4187
4384
  if (!await checkAdmin(req, url)) {
4188
4385
  return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
4189
4386
  }
4190
- const sessions = await store.getSessions();
4387
+ const allSessions = await store.getSessions();
4388
+ const sessions = resolveOnlineSessions($asai, hostdir, allSessions);
4191
4389
  return sendSuccess(
4192
4390
  res,
4193
4391
  {
@@ -4197,7 +4395,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4197
4395
  ip: s.ip,
4198
4396
  createdAt: s.createdAt,
4199
4397
  expiresAt: s.expiresAt,
4200
- lastActivityAt: s.lastActivityAt
4398
+ lastActivityAt: s.lastActivityAt,
4399
+ online: true
4201
4400
  }))
4202
4401
  },
4203
4402
  opt
@@ -4215,12 +4414,13 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
4215
4414
  if (!await checkAdmin(req, url)) {
4216
4415
  return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
4217
4416
  }
4218
- const sessions = await store.getSessions();
4417
+ await purgeOfflineSessions($asai, hostdir, store);
4418
+ const onlineCount = await countOnlineSessions($asai, hostdir, store);
4219
4419
  const quota = getQuota2($asai);
4220
4420
  return sendSuccess(
4221
4421
  res,
4222
4422
  {
4223
- sessions: sessions.length,
4423
+ sessions: onlineCount,
4224
4424
  maxLoginSessions: quota.maxLoginSessions ?? 100,
4225
4425
  quotaEnabled: !!quota.enabled,
4226
4426
  http: $asai.connectionshttp,
@@ -4295,10 +4495,10 @@ var As_default = {
4295
4495
  return relativePath;
4296
4496
  },
4297
4497
  //创建多层文件夹 同步
4298
- ensureDir(fs13, relativePath) {
4498
+ ensureDir(fs12, relativePath) {
4299
4499
  const absPathDir = this.toDirPath(relativePath);
4300
4500
  const absPath = this.toAbsolutePath(relativePath);
4301
- if (!fs13.existsSync(absPath)) {
4501
+ if (!fs12.existsSync(absPath)) {
4302
4502
  let pathTmp = "";
4303
4503
  absPathDir.split("/").forEach((el) => {
4304
4504
  if (el) {
@@ -4306,8 +4506,8 @@ var As_default = {
4306
4506
  pathTmp += "/";
4307
4507
  }
4308
4508
  pathTmp += el;
4309
- if (!fs13.existsSync(pathTmp)) {
4310
- if (!fs13.mkdirSync(pathTmp, { recursive: true })) {
4509
+ if (!fs12.existsSync(pathTmp)) {
4510
+ if (!fs12.mkdirSync(pathTmp, { recursive: true })) {
4311
4511
  return absPath;
4312
4512
  }
4313
4513
  }
@@ -4316,11 +4516,11 @@ var As_default = {
4316
4516
  }
4317
4517
  return absPath;
4318
4518
  },
4319
- makeDir(fs13, filePaths) {
4320
- return new Promise((resolve7, reject) => {
4519
+ makeDir(fs12, filePaths) {
4520
+ return new Promise((resolve6, reject) => {
4321
4521
  try {
4322
- const newPath = this.ensureDir(fs13, filePaths);
4323
- resolve7(newPath);
4522
+ const newPath = this.ensureDir(fs12, filePaths);
4523
+ resolve6(newPath);
4324
4524
  } catch (err) {
4325
4525
  reject(err);
4326
4526
  }
@@ -4543,7 +4743,10 @@ var AsAES_default = /* @__PURE__ */ __name(($asai, cfg = null) => {
4543
4743
  return fn(reqData);
4544
4744
  }
4545
4745
  } catch (error) {
4546
- return reqData;
4746
+ if (validateFields(reqData, ["data", "iv", "auth"])) {
4747
+ throw error;
4748
+ }
4749
+ return fn(reqData);
4547
4750
  }
4548
4751
  }
4549
4752
  __name(reqAES, "reqAES");
@@ -4627,6 +4830,48 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4627
4830
  return limittmp;
4628
4831
  }
4629
4832
  __name(reqlimit, "reqlimit");
4833
+ function decodePathSegment(seg) {
4834
+ try {
4835
+ return decodeURIComponent(seg);
4836
+ } catch {
4837
+ return seg;
4838
+ }
4839
+ }
4840
+ __name(decodePathSegment, "decodePathSegment");
4841
+ function bareFileName(name) {
4842
+ const base = String(name).replace(/\.json$/i, "");
4843
+ const slash = base.lastIndexOf("/");
4844
+ return slash > -1 ? base.substring(slash + 1) : base;
4845
+ }
4846
+ __name(bareFileName, "bareFileName");
4847
+ function parseFileBody(raw) {
4848
+ let body = raw;
4849
+ if (typeof body === "string") {
4850
+ try {
4851
+ body = JSON.parse(body);
4852
+ } catch {
4853
+ return { content: body };
4854
+ }
4855
+ }
4856
+ if (body && typeof body === "object" && !Array.isArray(body)) {
4857
+ return {
4858
+ name: body.name != null ? String(body.name) : void 0,
4859
+ content: body.content !== void 0 ? body.content : body
4860
+ };
4861
+ }
4862
+ return { content: body };
4863
+ }
4864
+ __name(parseFileBody, "parseFileBody");
4865
+ function fileContentString(content) {
4866
+ if (content == null) return "";
4867
+ if (typeof content === "string") return content;
4868
+ return JSON.stringify(content);
4869
+ }
4870
+ __name(fileContentString, "fileContentString");
4871
+ function fileKeyFromSegment(seg, bodyName) {
4872
+ return bareFileName(decodePathSegment(seg || bodyName || ""));
4873
+ }
4874
+ __name(fileKeyFromSegment, "fileKeyFromSegment");
4630
4875
  function getSqlParamsByUrl(req, opt) {
4631
4876
  let tmpSqlParams;
4632
4877
  const reqArr = req?.url?.split("?")[0]?.split("/") || [];
@@ -4640,8 +4885,10 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4640
4885
  if (reqArr[4] === "insert") {
4641
4886
  tmpSqlParams.type = reqArr[4];
4642
4887
  if (cfg?.type === "file") {
4888
+ const { name: bodyName, content } = parseFileBody(opt.data);
4889
+ const fileKey = fileKeyFromSegment(reqArr[5], bodyName);
4643
4890
  tmpSqlParams.field = cfg.field.data;
4644
- tmpSqlParams.value = [[reqwherestr(reqArr[5]), reqwherestr(opt.data)]];
4891
+ tmpSqlParams.value = [[fileKey, fileContentString(content)]];
4645
4892
  } else {
4646
4893
  tmpSqlParams.field = [];
4647
4894
  tmpSqlParams.value = [];
@@ -4661,9 +4908,12 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4661
4908
  }
4662
4909
  } else if (reqArr[4] === "update") {
4663
4910
  tmpSqlParams.type = reqArr[4];
4664
- tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(reqArr[5])]];
4911
+ const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
4912
+ tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
4665
4913
  if (cfg?.type === "file") {
4666
- tmpSqlParams.set = [[cfg.field.key, reqwherestr(opt.data)]];
4914
+ const { content } = parseFileBody(opt.data);
4915
+ const contentField = cfg.field.data?.[1] || "content";
4916
+ tmpSqlParams.set = [[contentField, fileContentString(content)]];
4667
4917
  } else {
4668
4918
  tmpSqlParams.set = [];
4669
4919
  const dbData = typeof opt.data === "string" ? JSON.parse(opt.data) : opt.data;
@@ -4679,13 +4929,15 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4679
4929
  }
4680
4930
  } else if (reqArr[4] === "delete") {
4681
4931
  tmpSqlParams.type = reqArr[4];
4682
- tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(reqArr[5])]];
4932
+ const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
4933
+ tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
4683
4934
  } else {
4684
4935
  if (cfg?.as > 1) {
4685
4936
  tmpSqlParams.as = +cfg.as - 1;
4686
4937
  }
4687
4938
  tmpSqlParams.field = cfg.field.data;
4688
- tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(reqArr[5])]];
4939
+ const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
4940
+ tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
4689
4941
  }
4690
4942
  } else {
4691
4943
  tmpSqlParams.as = cfg?.as;
@@ -4701,19 +4953,50 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4701
4953
  if (req?.url?.includes("/selectlist/")) {
4702
4954
  const list = Array.isArray(resdb) ? resdb : resdb?.data;
4703
4955
  if (Array.isArray(list)) {
4704
- const mapped = list.map((el) => {
4956
+ const names = list.map((el) => {
4957
+ if (typeof el === "string") {
4958
+ const dot2 = el.lastIndexOf(".");
4959
+ return dot2 > -1 ? el.substring(0, dot2) : el;
4960
+ }
4705
4961
  const rawName = el.name ?? el.file ?? "";
4706
4962
  let tmpName = String(rawName).substring(String(rawName).lastIndexOf("/") + 1);
4707
4963
  const dot = tmpName.lastIndexOf(".");
4708
4964
  return dot > -1 ? tmpName.substring(0, dot) : tmpName;
4709
4965
  });
4710
- if (Array.isArray(resdb)) return mapped;
4711
- resdb.data = mapped;
4966
+ const children = names.map((name) => ({ name, path: name }));
4967
+ if (Array.isArray(resdb)) return { children };
4968
+ resdb.data = children;
4969
+ return resdb;
4712
4970
  }
4713
4971
  }
4714
4972
  return resdb;
4715
4973
  }
4716
4974
  __name(dbFresh, "dbFresh");
4975
+ function normalizeSelectResult(params, resdb) {
4976
+ let data = resdb;
4977
+ if (params?.where?.length && Array.isArray(data) && data.length === 1) {
4978
+ data = data[0];
4979
+ }
4980
+ if (cfg?.type === "file" && data && typeof data === "object" && !Array.isArray(data)) {
4981
+ const nameKey = cfg.field?.data?.[0] || "name";
4982
+ const contentKey = cfg.field?.data?.[1] || "content";
4983
+ if (data[nameKey] != null) {
4984
+ data[nameKey] = bareFileName(String(data[nameKey]));
4985
+ }
4986
+ const raw = data[contentKey];
4987
+ if (typeof raw === "string") {
4988
+ const text = raw.trim();
4989
+ if (text.startsWith("{") || text.startsWith("[")) {
4990
+ try {
4991
+ data[contentKey] = JSON.parse(text);
4992
+ } catch {
4993
+ }
4994
+ }
4995
+ }
4996
+ }
4997
+ return data;
4998
+ }
4999
+ __name(normalizeSelectResult, "normalizeSelectResult");
4717
5000
  function dbCodePm(req, resdb) {
4718
5001
  if (req?.Userinfo && req?.Userinfo[2]) {
4719
5002
  resdb.data = $asai.$lib.AsCode.asencode(JSON.stringify(resdb.data), req.Userinfo[2]);
@@ -4744,7 +5027,7 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
4744
5027
  if (params && typeof DataServer.sqlDb === "function") {
4745
5028
  try {
4746
5029
  DataServer.sqlDb(params).then((resdb) => {
4747
- let resData = dbFresh(req, resdb);
5030
+ let resData = normalizeSelectResult(params, dbFresh(req, resdb));
4748
5031
  if (params?.as === 2 && params?.field?.length > 1) {
4749
5032
  let limitArr = [];
4750
5033
  if (params.limit?.split(",")?.length < 2) {
@@ -5055,19 +5338,31 @@ var lib_default = {
5055
5338
  };
5056
5339
 
5057
5340
  // src/sysserver/db/src/common.ts
5341
+ var common_exports = {};
5342
+ __export(common_exports, {
5343
+ fieldHasId: () => fieldHasId,
5344
+ isPathInside: () => isPathInside,
5345
+ isTableMissingError: () => isTableMissingError,
5346
+ joinTablePath: () => joinTablePath,
5347
+ promisifySqlDb: () => promisifySqlDb,
5348
+ toFieldArray: () => toFieldArray,
5349
+ toInsertRows: () => toInsertRows,
5350
+ toSetObject: () => toSetObject,
5351
+ toWherePairs: () => toWherePairs
5352
+ });
5058
5353
  import * as path5 from "path";
5059
5354
  function promisifySqlDb(query, sql) {
5060
- return new Promise((resolve7, reject) => {
5355
+ return new Promise((resolve6, reject) => {
5061
5356
  query(sql, (err, result) => {
5062
5357
  if (err) {
5063
5358
  reject(err);
5064
5359
  return;
5065
5360
  }
5066
5361
  if (!result || Array.isArray(result) && result.length === 0) {
5067
- resolve7("");
5362
+ resolve6("");
5068
5363
  return;
5069
5364
  }
5070
- resolve7(result);
5365
+ resolve6(result);
5071
5366
  });
5072
5367
  });
5073
5368
  }
@@ -5077,6 +5372,48 @@ function toFieldArray(field) {
5077
5372
  return typeof field === "string" ? field.split(",") : field;
5078
5373
  }
5079
5374
  __name(toFieldArray, "toFieldArray");
5375
+ function toWherePairs(where) {
5376
+ if (!where || !Array.isArray(where)) return [];
5377
+ const pairs = [];
5378
+ for (const item of where) {
5379
+ if (typeof item === "string") continue;
5380
+ if (Array.isArray(item) && item.length >= 3) {
5381
+ pairs.push([item[0], item[2]]);
5382
+ }
5383
+ }
5384
+ return pairs;
5385
+ }
5386
+ __name(toWherePairs, "toWherePairs");
5387
+ function toSetObject(set) {
5388
+ if (!set) return {};
5389
+ if (typeof set === "string") return {};
5390
+ const obj = {};
5391
+ for (const item of set) {
5392
+ if (typeof item === "string") continue;
5393
+ if (Array.isArray(item)) {
5394
+ if (item.length >= 3 && typeof item[1] === "string") {
5395
+ obj[item[0]] = item[2];
5396
+ } else if (item.length >= 2) {
5397
+ obj[item[0]] = item[1];
5398
+ }
5399
+ }
5400
+ }
5401
+ return obj;
5402
+ }
5403
+ __name(toSetObject, "toSetObject");
5404
+ function toInsertRows(sql) {
5405
+ const fields = toFieldArray(sql.field);
5406
+ const values = sql.value;
5407
+ if (!fields.length || !Array.isArray(values)) return [];
5408
+ return values.map((row) => {
5409
+ const rowObj = {};
5410
+ fields.forEach((field, index) => {
5411
+ rowObj[field] = row[index];
5412
+ });
5413
+ return rowObj;
5414
+ });
5415
+ }
5416
+ __name(toInsertRows, "toInsertRows");
5080
5417
  function isPathInside(baseDir, targetPath) {
5081
5418
  const base = path5.resolve(baseDir);
5082
5419
  const target = path5.resolve(targetPath);
@@ -5262,936 +5599,426 @@ var Sql_default = {
5262
5599
  }
5263
5600
  };
5264
5601
 
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
5602
+ // src/sysserver/db/src/file/Index.ts
5342
5603
  import * as fs11 from "fs";
5343
5604
  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");
5605
+ function dbDriver(opt = {}) {
5606
+ const { Sql, DbCommon } = opt;
5607
+ const { isPathInside: isPathInside2, joinTablePath: joinTablePath2 } = DbCommon;
5608
+ class DbFile {
5609
+ static {
5610
+ __name(this, "DbFile");
5611
+ }
5612
+ constructor(config) {
5613
+ this.config = config;
5614
+ this.fileExt = config.ext === ".*" ? "" : config.ext || "";
5615
+ this.fileDel = (config.del || 0) !== 0;
5616
+ this.createDir = config.create || false;
5617
+ this.baseDirAbs = path6.resolve(process.cwd(), config.dir);
5618
+ }
5619
+ dbLog(...args) {
5620
+ (this.config?.$asai?.$log || console.log)("FILE", ...args);
5621
+ }
5622
+ checkFile(file) {
5623
+ return fs11.existsSync(this.getAbsPath(file, false));
5624
+ }
5625
+ /** 解析相对路径并限制在数据根目录内,防止路径穿越(跨平台) */
5626
+ resolvePath(relPath) {
5627
+ if (!relPath) {
5628
+ return this.baseDirAbs;
5629
+ }
5630
+ const abs = path6.isAbsolute(relPath) ? path6.normalize(relPath) : path6.resolve(this.baseDirAbs, relPath);
5631
+ if (!isPathInside2(this.baseDirAbs, abs)) {
5632
+ return this.baseDirAbs;
5633
+ }
5634
+ return abs;
5635
+ }
5636
+ /** 将 table 与文件名片段拼为平台无关相对路径 */
5637
+ tableFilePath(table, ...segments) {
5638
+ return joinTablePath2(table, ...segments);
5639
+ }
5640
+ normalizeRelPath(relPath) {
5641
+ if (!relPath) {
5642
+ return "";
5484
5643
  }
5485
- } catch (err) {
5486
- this.dbLog("Database create failed", "[CONN] Failed to create connection:", err.message);
5487
- throw err;
5644
+ return path6.isAbsolute(relPath) ? path6.relative(this.baseDirAbs, relPath) : relPath;
5488
5645
  }
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");
5646
+ ensureDir(relPath) {
5647
+ const relative4 = this.normalizeRelPath(relPath);
5648
+ const dirPath = path6.dirname(relative4);
5649
+ const absDir = this.resolvePath(dirPath === "." ? "" : dirPath);
5650
+ if (this.createDir && !fs11.existsSync(absDir)) {
5651
+ this.dbLog("mkdir", "[MKDIR]", absDir);
5652
+ fs11.mkdirSync(absDir, { recursive: true });
5653
+ }
5654
+ return absDir;
5500
5655
  }
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;
5656
+ getAbsPath(file, autoCreateDir = true) {
5657
+ let rel = file;
5658
+ if (this.fileExt && !rel.endsWith(this.fileExt)) {
5659
+ rel += this.fileExt;
5515
5660
  }
5661
+ if (autoCreateDir) {
5662
+ this.ensureDir(rel);
5663
+ }
5664
+ return this.resolvePath(rel);
5516
5665
  }
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;
5666
+ deldir(relativePath) {
5667
+ const absPath = this.resolvePath(relativePath);
5668
+ if (!fs11.existsSync(absPath)) {
5669
+ return;
5670
+ }
5671
+ this.dbLog("delete", "[RMDIR]", absPath);
5672
+ const entries = fs11.readdirSync(absPath, { withFileTypes: true });
5673
+ for (const entry of entries) {
5674
+ const current = path6.join(absPath, entry.name);
5675
+ if (entry.isDirectory()) {
5676
+ this.deldir(path6.join(relativePath, entry.name));
5526
5677
  } else {
5527
- await conn.close().catch(() => {
5528
- });
5678
+ this.dbLog("delete", "[UNLINK]", current);
5679
+ fs11.unlinkSync(current);
5529
5680
  }
5530
- } catch (e) {
5531
- this.dbLog("create failed", "[ACQUIRE] Failed to create new connection, waiting:", e.message);
5532
5681
  }
5682
+ fs11.rmdirSync(absPath);
5533
5683
  }
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);
5684
+ getReadPath(file, sql) {
5685
+ const candidates = [];
5686
+ if (sql?.filename) {
5687
+ candidates.push({ dir: path6.join(file, sql.filename) });
5688
+ candidates.push({ dir: path6.join(file + ".delete" + this.fileExt, sql.filename) });
5689
+ } else {
5690
+ candidates.push({ dir: file });
5691
+ candidates.push({ dir: file + this.fileExt + ".delete" });
5692
+ }
5693
+ for (const candidate of candidates) {
5694
+ const abs = this.getAbsPath(candidate.dir, false);
5695
+ if (fs11.existsSync(abs)) {
5696
+ return { path: abs, dir: candidate.dir };
5576
5697
  }
5577
5698
  }
5578
- for (const conn of toRemove) {
5579
- this.removeConnection(conn);
5699
+ return { path: "", dir: "" };
5700
+ }
5701
+ getDelPath(file, sql) {
5702
+ const read2 = this.getReadPath(file, sql);
5703
+ if (!read2.path) {
5704
+ return { path: "", dir: read2.dir };
5580
5705
  }
5581
- if (toRemove.length > 0) {
5582
- this.dbLog("Reclaimed", "[IDLE] Reclaimed", toRemove.length, "idle connections, current pool size:", this.pool.length);
5706
+ if (sql?.filename) {
5707
+ return { path: path6.dirname(read2.path), dir: path6.dirname(read2.dir) };
5583
5708
  }
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"));
5709
+ return { path: read2.path, dir: read2.dir };
5609
5710
  }
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);
5711
+ getNewPath(file, sql) {
5712
+ if (sql?.filename) {
5713
+ return this.getAbsPath(path6.join(file, sql.filename), true);
5714
+ }
5715
+ return this.getAbsPath(file, true);
5638
5716
  }
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");
5717
+ write(file, data, sql) {
5696
5718
  try {
5697
- if (sql.type === "select") {
5698
- db.all(sqlStr, finish);
5699
- } else {
5700
- db.run(sqlStr, (err) => {
5701
- finish(err, "OK");
5702
- });
5703
- }
5719
+ const filePath = sql?.type === "insert" ? this.getNewPath(file, sql) : this.getReadPath(file, sql).path || this.getNewPath(file, sql);
5720
+ this.dbLog("write", "[WRITE]", filePath);
5721
+ fs11.writeFileSync(filePath, data, "utf8");
5722
+ return "ok";
5704
5723
  } 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);
5724
+ this.dbLog("write failed", "[WRITE ERROR]", e);
5725
+ return e;
5735
5726
  }
5736
- throw err;
5737
5727
  }
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;
5728
+ read(file, sql, encoding = "utf8") {
5747
5729
  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);
5730
+ const read2 = this.getReadPath(file, sql);
5731
+ if (!read2.path) {
5732
+ return { error: "empty" };
5733
+ }
5734
+ this.dbLog("read", "[READ]", read2.path);
5735
+ const data = fs11.readFileSync(read2.path, { encoding }) || "";
5736
+ const fileName = this.fileExt && read2.dir.endsWith(this.fileExt) ? read2.dir : read2.dir + this.fileExt;
5737
+ return [{ data, file: fileName }];
5756
5738
  } catch (e) {
5757
- this.dbLog("Creation failed", "[TABLE] Creation failed:", e);
5758
- } finally {
5759
- if (conn) this.pool.release(conn);
5760
- }
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);
5739
+ this.dbLog("read failed", "[READ ERROR]", e);
5740
+ return e;
5869
5741
  }
5870
5742
  }
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
- }
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);
5743
+ del(file, sql) {
5744
+ try {
5745
+ const del = this.getDelPath(file, sql);
5746
+ if (!del.path) {
5747
+ return "";
5748
+ }
5749
+ if (this.fileDel && sql?.deltrue) {
5750
+ const target = del.path + ".delete" + this.fileExt;
5751
+ this.dbLog("soft delete", "[SOFT DELETE]", `${del.path} -> ${target}`);
5752
+ fs11.renameSync(del.path, target);
5753
+ } else if (sql?.filename) {
5754
+ this.deldir(del.dir);
5755
+ } else {
5756
+ this.dbLog("delete", "[DELETE]", del.path);
5757
+ fs11.unlinkSync(del.path);
5758
+ }
5759
+ return "ok";
5760
+ } catch (e) {
5761
+ this.dbLog("delete failed", "[DELETE ERROR]", e);
5762
+ return e;
5947
5763
  }
5948
- return "ok";
5949
- } catch (e) {
5950
- this.dbLog("delete failed", "[DELETE ERROR]", e);
5951
- return e;
5952
5764
  }
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);
5765
+ re(file, sql) {
5766
+ try {
5767
+ const del = this.getDelPath(file, sql);
5768
+ if (!del.path) {
5769
+ return "";
5966
5770
  }
5967
- } else {
5968
- this.dbLog("force delete", "[FORCE DELETE]", del.path);
5969
- fs12.unlinkSync(del.path);
5771
+ if (this.fileDel) {
5772
+ const extPattern = this.fileExt ? this.fileExt.replace(/\./g, "\\.") : "";
5773
+ const original = del.path.replace(new RegExp(`.delete${extPattern}$`), "");
5774
+ if (original !== del.path) {
5775
+ this.dbLog("restore", "[RESTORE]", `${del.path} -> ${original}`);
5776
+ fs11.renameSync(del.path, original);
5777
+ }
5778
+ } else {
5779
+ this.dbLog("force delete", "[FORCE DELETE]", del.path);
5780
+ fs11.unlinkSync(del.path);
5781
+ }
5782
+ return "ok";
5783
+ } catch (e) {
5784
+ this.dbLog("restore failed", "[RESTORE ERROR]", e);
5785
+ return e;
5970
5786
  }
5971
- return "ok";
5972
- } catch (e) {
5973
- this.dbLog("restore failed", "[RESTORE ERROR]", e);
5974
- return e;
5975
5787
  }
5976
- }
5977
- list(dir, sql) {
5978
- try {
5979
- const absDir = this.resolvePath(dir);
5980
- this.dbLog("list", "[LIST]", absDir);
5981
- let entries;
5788
+ list(dir, sql) {
5982
5789
  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);
5790
+ const absDir = this.resolvePath(dir);
5791
+ this.dbLog("list", "[LIST]", absDir);
5792
+ let entries;
5996
5793
  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
- }
5794
+ entries = fs11.readdirSync(absDir, { withFileTypes: true });
6004
5795
  } catch {
5796
+ return sql?.list ? { open: [], delete: [] } : [];
5797
+ }
5798
+ const result = { open: [], delete: [] };
5799
+ const deleteSuffix = ".delete" + this.fileExt;
5800
+ for (const entry of entries) {
5801
+ if (!entry.isFile()) continue;
5802
+ const fileName = entry.name;
5803
+ const isDelete = fileName.endsWith(deleteSuffix);
5804
+ const baseName = isDelete ? fileName.slice(0, -deleteSuffix.length) : fileName;
5805
+ if (this.fileExt && !baseName.endsWith(this.fileExt)) continue;
5806
+ const fullPath = path6.join(absDir, fileName);
5807
+ try {
5808
+ const data = fs11.readFileSync(fullPath, "utf8");
5809
+ const entryData = { data, file: baseName };
5810
+ if (isDelete) {
5811
+ if (sql?.list) result.delete.push(entryData);
5812
+ } else {
5813
+ result.open.push(entryData);
5814
+ }
5815
+ } catch {
5816
+ }
6005
5817
  }
5818
+ return sql?.list ? result : result.open;
5819
+ } catch (e) {
5820
+ this.dbLog("list failed", "[LIST ERROR]", e);
5821
+ return e;
6006
5822
  }
6007
- return sql?.list ? result : result.open;
6008
- } catch (e) {
6009
- this.dbLog("list failed", "[LIST ERROR]", e);
6010
- return e;
6011
5823
  }
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, "");
5824
+ /** 从 where 首条条件中提取文件名(去掉 SQL 风格引号) */
5825
+ getFileNameFromWhere(where) {
5826
+ if (!where || !Array.isArray(where)) return "";
5827
+ const first = where.find((item) => Array.isArray(item));
5828
+ const raw = first?.[2];
5829
+ if (typeof raw === "string") {
5830
+ return raw.replace(/^'+|'+$/g, "");
5831
+ }
5832
+ return String(raw ?? "");
6020
5833
  }
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) {
5834
+ sqlDb(sql) {
5835
+ return new Promise((resolve6, reject) => {
5836
+ try {
5837
+ let result;
5838
+ const tableDir = this.tableFilePath(sql.table);
5839
+ if (sql.type === "insert") {
5840
+ const [file, data] = sql.value[0];
5841
+ result = this.write(this.tableFilePath(sql.table, file), data, sql);
5842
+ } else if (sql.type === "delete") {
6043
5843
  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);
5844
+ result = this.del(this.tableFilePath(sql.table, file), sql);
5845
+ } else if (sql.type === "re") {
5846
+ const file = this.getFileNameFromWhere(sql.where);
5847
+ result = this.re(this.tableFilePath(sql.table, file), sql);
5848
+ } else if (sql.type === "update") {
5849
+ const file = this.getFileNameFromWhere(sql.where);
5850
+ const data = sql.set[0][1];
5851
+ result = this.write(this.tableFilePath(sql.table, file), data, sql);
5852
+ } else if (sql.type === "select") {
5853
+ if (sql?.where?.length && sql?.as !== 2) {
5854
+ const file = this.getFileNameFromWhere(sql.where);
5855
+ const readRes = this.read(this.tableFilePath(sql.table, file), sql);
5856
+ const resObj = {};
5857
+ if (readRes?.[0]?.file && !readRes.error) {
5858
+ if (sql.as === 1) {
5859
+ try {
5860
+ const parsed = JSON.parse(readRes[0].data);
5861
+ if (sql.field?.length) {
5862
+ sql.field.forEach((f) => {
5863
+ resObj[f] = parsed[f] ?? "";
5864
+ });
5865
+ } else {
5866
+ Object.assign(resObj, parsed);
5867
+ }
5868
+ } catch {
5869
+ }
5870
+ } else {
5871
+ const fields = sql.field;
5872
+ resObj[fields[0]] = readRes[0].file;
5873
+ if (fields[1]) {
5874
+ resObj[fields[1]] = readRes[0].data;
6056
5875
  }
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
5876
  }
6065
5877
  }
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 {
5878
+ result = [resObj];
5879
+ } else {
5880
+ const listRes = this.list(tableDir, sql);
5881
+ if (listRes && !listRes.error) {
5882
+ if (sql?.list) {
5883
+ const mapEntry = /* @__PURE__ */ __name((el) => {
5884
+ const fields = sql.field;
5885
+ const item = { [fields[0]]: el.file };
5886
+ if (fields[1]) item[fields[1]] = el.data;
5887
+ return item;
5888
+ }, "mapEntry");
5889
+ if (listRes.open) listRes.open = listRes.open.map(mapEntry);
5890
+ if (listRes.delete) listRes.delete = listRes.delete.map(mapEntry);
5891
+ result = listRes;
5892
+ } else {
5893
+ if (sql?.as === 2) {
5894
+ let items = listRes;
5895
+ if (sql.where?.length) {
5896
+ const where = sql.where;
5897
+ const whereType = typeof where[0] === "string" ? where[0] : "and";
5898
+ items = items.filter((el) => {
5899
+ let match = whereType === "and";
5900
+ for (let i = 0; i < where.length; i++) {
5901
+ if (typeof where[i] === "string") continue;
5902
+ const cond = where[i];
5903
+ if (!cond) continue;
5904
+ const field = cond[0];
5905
+ const searchVal = String(cond[2]).replace(/%/g, "");
5906
+ let fieldVal = "";
5907
+ try {
5908
+ fieldVal = JSON.parse(el.data)[field] ?? "";
5909
+ } catch {
5910
+ }
5911
+ const condMatch = String(fieldVal).includes(searchVal);
5912
+ if (whereType === "and") {
5913
+ match = match && condMatch;
5914
+ if (!match) break;
5915
+ } else {
5916
+ match = match || condMatch;
5917
+ if (match) break;
5918
+ }
6099
5919
  }
6100
- const condMatch = String(fieldVal).includes(searchVal);
6101
- if (whereType === "and") {
6102
- match = match && condMatch;
6103
- if (!match) break;
5920
+ return match;
5921
+ });
5922
+ }
5923
+ items = items.map((el) => {
5924
+ const obj = {};
5925
+ try {
5926
+ const parsed = JSON.parse(el.data);
5927
+ if (sql.field?.length) {
5928
+ sql.field.forEach((f) => {
5929
+ obj[f] = parsed[f] ?? "";
5930
+ });
6104
5931
  } else {
6105
- match = match || condMatch;
6106
- if (match) break;
5932
+ Object.assign(obj, parsed);
6107
5933
  }
5934
+ } catch {
6108
5935
  }
6109
- return match;
5936
+ return obj;
6110
5937
  });
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 {
5938
+ if (sql.order?.length) {
5939
+ const orderField = sql.order[0];
5940
+ const orderFieldName = typeof orderField === "string" ? orderField : orderField[0];
5941
+ const direction = typeof orderField === "string" ? "ASC" : String(orderField[1]).toUpperCase();
5942
+ items.sort((a, b) => {
5943
+ const valA = String(a[orderFieldName] ?? "");
5944
+ const valB = String(b[orderFieldName] ?? "");
5945
+ const cmp = valA.localeCompare(valB);
5946
+ return direction === "DESC" ? -cmp : cmp;
5947
+ });
6124
5948
  }
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
- });
5949
+ result = items;
5950
+ } else {
5951
+ const fields = sql.field;
5952
+ result = listRes.map((el) => ({
5953
+ [fields[0]]: el.file,
5954
+ ...fields[1] ? { [fields[1]]: el.data } : {}
5955
+ }));
6137
5956
  }
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
5957
  }
5958
+ } else {
5959
+ result = sql?.list ? { open: [], delete: [] } : [];
6146
5960
  }
6147
- } else {
6148
- result = sql?.list ? { open: [], delete: [] } : [];
6149
5961
  }
6150
5962
  }
5963
+ if (result !== void 0) {
5964
+ resolve6(result);
5965
+ } else {
5966
+ reject("");
5967
+ }
5968
+ } catch (e) {
5969
+ reject(e);
6151
5970
  }
6152
- if (result !== void 0) {
6153
- resolve7(result);
6154
- } else {
6155
- reject("");
6156
- }
6157
- } catch (e) {
6158
- reject(e);
6159
- }
6160
- });
5971
+ });
5972
+ }
6161
5973
  }
6162
- };
6163
- var Index_default4 = DbFile;
5974
+ return DbFile;
5975
+ }
5976
+ __name(dbDriver, "dbDriver");
5977
+ var Index_default2 = dbDriver;
6164
5978
 
6165
5979
  // 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);
5980
+ var BUILTIN_FILE = "file";
5981
+ function buildFileDriver() {
5982
+ return Index_default2({ IdbType: {}, Sql: Sql_default, DbCommon: common_exports });
5983
+ }
5984
+ __name(buildFileDriver, "buildFileDriver");
5985
+ function resolveDriver(type, dbDrivers) {
5986
+ if (type === BUILTIN_FILE) {
5987
+ return buildFileDriver();
5988
+ }
5989
+ const factory = dbDrivers[type];
5990
+ if (typeof factory !== "function") {
5991
+ return null;
6175
5992
  }
6176
- return {};
5993
+ return factory({ IdbType: {}, Sql: Sql_default, DbCommon: common_exports });
6177
5994
  }
6178
- __name(initDb, "initDb");
5995
+ __name(resolveDriver, "resolveDriver");
5996
+ var initDb = /* @__PURE__ */ __name((type = BUILTIN_FILE, config = {}, dbDrivers = {}) => {
5997
+ let Driver = resolveDriver(type, dbDrivers);
5998
+ if (!Driver) {
5999
+ if (type !== BUILTIN_FILE) {
6000
+ console.warn(
6001
+ `[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`
6002
+ );
6003
+ }
6004
+ Driver = buildFileDriver();
6005
+ }
6006
+ return new Driver(config);
6007
+ }, "initDb");
6179
6008
  var db_default = initDb;
6180
6009
 
6181
6010
  // src/sysserver/index.ts
6182
- var sysserver_default = /* @__PURE__ */ __name(($asai) => {
6011
+ function sysserver($asai, opt = {}) {
6183
6012
  if (!$asai.$lib) $asai.$lib = {};
6013
+ $asai.$lib = { ...lib_default, ...$asai.$lib, ...opt || {} };
6184
6014
  $asai.dataserver = db_default;
6185
- $asai.$lib = { ...lib_default, ...$asai.$lib };
6186
- if (!$asai.asaifs) {
6187
- $asai.asaifs = $asai.$lib.AsFs;
6188
- }
6189
6015
  $asai.$lib.ErrorCode?.loadErrorCodes?.($asai);
6190
6016
  if (!$asai.$lib.api) $asai.$lib.api = {};
6191
6017
  $asai.$lib.api = { ...api_default, ...$asai.$lib.api };
6192
6018
  if (!$asai.$lib.ws) $asai.$lib.ws = {};
6193
6019
  $asai.$lib.ws = { ...ws_default, ...$asai.$lib.ws };
6194
- }, "default");
6020
+ }
6021
+ __name(sysserver, "sysserver");
6195
6022
 
6196
6023
  // src/config/validate.ts
6197
6024
  function normalizeHostConfig(cfg) {
@@ -6306,6 +6133,12 @@ function normalizeUserStoreConfig(cfg) {
6306
6133
  } else if (store.type === "sqlite") {
6307
6134
  if (!store.opt.database) store.opt.database = "./webdata/webdb/users/users.db";
6308
6135
  if (store.opt.create == null) store.opt.create = 1;
6136
+ } else if (store.type === "mysql") {
6137
+ if (!store.opt.host) store.opt.host = "127.0.0.1";
6138
+ if (!store.opt.user) store.opt.user = "root";
6139
+ if (store.opt.password == null) store.opt.password = "";
6140
+ if (!store.opt.database) store.opt.database = "asai";
6141
+ if (store.opt.create == null) store.opt.create = 1;
6309
6142
  }
6310
6143
  }
6311
6144
  __name(normalizeUserStoreConfig, "normalizeUserStoreConfig");
@@ -6348,6 +6181,30 @@ function normalizeDataStoresConfig(cfg) {
6348
6181
  if (!stores.user && cfg.userStore) {
6349
6182
  stores.user = { type: cfg.userStore.type || "file", opt: { ...cfg.userStore.opt || {} } };
6350
6183
  }
6184
+ if (!stores.user) {
6185
+ stores.user = {
6186
+ type: "file",
6187
+ opt: { dir: "webdata/webdb/users/", ext: ".json", create: 1 }
6188
+ };
6189
+ }
6190
+ if (stores.user && typeof stores.user === "object") {
6191
+ const userStore = stores.user;
6192
+ const userType = userStore.type || "file";
6193
+ if (!userStore.type) userStore.type = "file";
6194
+ if (!userStore.opt || typeof userStore.opt !== "object") userStore.opt = {};
6195
+ if (userType === "file") {
6196
+ if (!userStore.opt.dir) userStore.opt.dir = "webdata/webdb/users/";
6197
+ if (!userStore.opt.ext) userStore.opt.ext = ".json";
6198
+ } else if (userType === "sqlite") {
6199
+ if (!userStore.opt.database) userStore.opt.database = "./webdata/webdb/users/users.db";
6200
+ } else if (userType === "mysql") {
6201
+ if (!userStore.opt.host) userStore.opt.host = "127.0.0.1";
6202
+ if (!userStore.opt.user) userStore.opt.user = "root";
6203
+ if (userStore.opt.password == null) userStore.opt.password = "";
6204
+ if (!userStore.opt.database) userStore.opt.database = "asai";
6205
+ }
6206
+ if (userStore.opt.create == null) userStore.opt.create = 1;
6207
+ }
6351
6208
  if (!stores.log && cfg.logger?.store) {
6352
6209
  const s = cfg.logger.store;
6353
6210
  stores.log = {
@@ -6674,10 +6531,10 @@ function hostDefault(mod) {
6674
6531
  }
6675
6532
  __name(hostDefault, "hostDefault");
6676
6533
  var hostMain = hostDefault(src_default);
6677
- var initAsaiHost2 = {
6534
+ var initAsaiHostNodejs2 = {
6678
6535
  name: PLUGIN_NAME,
6679
6536
  fn: /* @__PURE__ */ __name(($asai, opt) => hostMain($asai, opt), "fn"),
6680
- sysserver: sysserver_default,
6537
+ sysserver,
6681
6538
  AsaiCommon: Index_default,
6682
6539
  AsaiUtils: utils_exports,
6683
6540
  normalizeHostConfig,
@@ -6714,8 +6571,8 @@ var initAsaiHost2 = {
6714
6571
  createTransportDataHandler,
6715
6572
  wireTransportConnectionLogs
6716
6573
  };
6717
- var PLUGIN_VERSION = true ? "0.0.1" : "0.0.1";
6718
- var index_default = initAsaiHost2;
6574
+ var PLUGIN_VERSION = true ? "0.0.3" : "0.0.1";
6575
+ var index_default = initAsaiHostNodejs2;
6719
6576
  export {
6720
6577
  Index_default as AsaiCommon,
6721
6578
  utils_exports as AsaiUtils,
@@ -6730,7 +6587,7 @@ export {
6730
6587
  getTransportBlock,
6731
6588
  getTransportDefault,
6732
6589
  hasTransportDefault,
6733
- initAsaiHost2 as initAsaiHost,
6590
+ initAsaiHostNodejs2 as initAsaiHostNodejs,
6734
6591
  initHostserverApi,
6735
6592
  initHostserverWs,
6736
6593
  initTransportBridge,
@@ -6751,7 +6608,7 @@ export {
6751
6608
  stripForbiddenSecretsFromHostConfig,
6752
6609
  syncApiRouteKeys,
6753
6610
  syncWsRouteKeys,
6754
- sysserver_default as sysserver,
6611
+ sysserver,
6755
6612
  validateAsaiSecrets,
6756
6613
  validateHostConfig,
6757
6614
  validateHttpAuth,