@yejiming/dsh-data-agent 0.0.9 → 0.0.11

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.
@@ -1,5 +1,7 @@
1
1
  import "./defaults-DP4RyRh1.js";
2
- import { resolve } from "node:path";
2
+ import { readdir } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { posix, resolve, win32 } from "node:path";
3
5
  import z from "schemastery";
4
6
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
5
7
  //#region src/sql.ts
@@ -595,7 +597,8 @@ function quoteStringLiteral(value) {
595
597
  /** Loader schema for one client override (all fields optional at input). */
596
598
  const clientConfigSchema = z.object({
597
599
  command: z.string(),
598
- args: z.array(z.string())
600
+ args: z.array(z.string()),
601
+ searchPaths: z.array(z.string())
599
602
  });
600
603
  /** Loader schema for the whole `clients` config object (any type key). */
601
604
  const clientsSchema = z.dict(clientConfigSchema).default({});
@@ -891,6 +894,291 @@ function parseColumns(type, stdout) {
891
894
  return columns;
892
895
  }
893
896
  //#endregion
897
+ //#region src/client-discovery.ts
898
+ /**
899
+ * Cross-platform database CLI discovery.
900
+ *
901
+ * The subprocess provider remains the authority for executable validation.
902
+ * This module only builds a bounded, platform-aware PATH fallback when the
903
+ * provider cannot resolve the configured/default bare command from its
904
+ * current execution environment. No shell, registry, or recursive scan is
905
+ * involved, and the exact discovery environment is returned for spawn.
906
+ * @module @yejiming/dsh-data-agent/client-discovery
907
+ */
908
+ /** Maximum child names consumed from one known version/formula directory. */
909
+ const MAX_DYNAMIC_ENTRIES = 64;
910
+ /** Production host facts. */
911
+ const DEFAULT_SYSTEM = {
912
+ platform: process.platform,
913
+ env: process.env,
914
+ homeDir: homedir(),
915
+ cwd: process.cwd(),
916
+ async readDirectory(directory) {
917
+ return await readdir(directory);
918
+ }
919
+ };
920
+ const HOME_ENV_BY_TYPE = {
921
+ mysql: ["MYSQL_HOME"],
922
+ postgres: ["PGHOME", "PGROOT"],
923
+ sqlite: ["SQLITE_HOME"],
924
+ oracle: ["ORACLE_HOME"],
925
+ hive: ["HIVE_HOME"],
926
+ impala: ["IMPALA_HOME"]
927
+ };
928
+ function pathApi(platform) {
929
+ return platform === "win32" ? win32 : posix;
930
+ }
931
+ function environmentValue(env, name, platform) {
932
+ if (platform !== "win32") return env[name];
933
+ const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
934
+ return key === void 0 ? void 0 : env[key];
935
+ }
936
+ function expandHome(directory, system, paths) {
937
+ const trimmed = directory.trim();
938
+ if (trimmed === "~") return system.homeDir;
939
+ if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) return paths.join(system.homeDir, trimmed.slice(2));
940
+ return paths.isAbsolute(trimmed) ? paths.normalize(trimmed) : paths.resolve(system.cwd, trimmed);
941
+ }
942
+ function normalizeDirectories(directories, system, paths) {
943
+ const result = [];
944
+ const seen = /* @__PURE__ */ new Set();
945
+ for (const raw of directories) {
946
+ if (raw.trim() === "") continue;
947
+ const directory = expandHome(raw, system, paths);
948
+ const key = system.platform === "win32" ? directory.toLowerCase() : directory;
949
+ if (seen.has(key)) continue;
950
+ seen.add(key);
951
+ result.push(directory);
952
+ }
953
+ return result;
954
+ }
955
+ function clientHomeDirectories(type, system, paths) {
956
+ const result = [];
957
+ for (const name of HOME_ENV_BY_TYPE[type]) {
958
+ const value = environmentValue(system.env, name, system.platform)?.trim();
959
+ if (value === void 0 || value === "") continue;
960
+ result.push(paths.join(value, "bin"), value);
961
+ }
962
+ return result;
963
+ }
964
+ function macFixedDirectories(type) {
965
+ return [
966
+ "/opt/homebrew/bin",
967
+ ...{
968
+ mysql: [
969
+ "/opt/homebrew/opt/mysql-client/bin",
970
+ "/opt/homebrew/opt/mysql/bin",
971
+ "/usr/local/opt/mysql-client/bin",
972
+ "/usr/local/opt/mysql/bin",
973
+ "/usr/local/mysql/bin"
974
+ ],
975
+ postgres: [
976
+ "/opt/homebrew/opt/libpq/bin",
977
+ "/usr/local/opt/libpq/bin",
978
+ "/Applications/Postgres.app/Contents/Versions/latest/bin"
979
+ ],
980
+ sqlite: ["/opt/homebrew/opt/sqlite/bin", "/usr/local/opt/sqlite/bin"],
981
+ oracle: [],
982
+ hive: ["/opt/homebrew/opt/hive/bin", "/usr/local/opt/hive/bin"],
983
+ impala: ["/opt/homebrew/opt/impala/bin", "/usr/local/opt/impala/bin"]
984
+ }[type],
985
+ "/usr/local/bin",
986
+ "/opt/local/bin",
987
+ "/usr/bin"
988
+ ];
989
+ }
990
+ function linuxFixedDirectories(system, paths) {
991
+ return [
992
+ paths.join(system.homeDir, ".local", "bin"),
993
+ "/home/linuxbrew/.linuxbrew/bin",
994
+ paths.join(system.homeDir, ".linuxbrew", "bin"),
995
+ "/usr/local/bin",
996
+ "/usr/bin",
997
+ "/snap/bin",
998
+ paths.join(system.homeDir, ".nix-profile", "bin"),
999
+ "/nix/var/nix/profiles/default/bin"
1000
+ ];
1001
+ }
1002
+ function windowsFixedDirectories(type, system, paths) {
1003
+ const localAppData = environmentValue(system.env, "LOCALAPPDATA", system.platform);
1004
+ const userProfile = environmentValue(system.env, "USERPROFILE", system.platform) ?? system.homeDir;
1005
+ const chocolatey = environmentValue(system.env, "ChocolateyInstall", system.platform);
1006
+ const programData = environmentValue(system.env, "ProgramData", system.platform) ?? "C:\\ProgramData";
1007
+ const programFiles = environmentValue(system.env, "ProgramFiles", system.platform) ?? "C:\\Program Files";
1008
+ const typeSpecific = {
1009
+ mysql: [],
1010
+ postgres: [],
1011
+ sqlite: [paths.join("C:\\", "sqlite"), paths.join(programFiles, "SQLite")],
1012
+ oracle: [],
1013
+ hive: [],
1014
+ impala: []
1015
+ };
1016
+ return [
1017
+ ...localAppData === void 0 ? [] : [paths.join(localAppData, "Microsoft", "WinGet", "Links")],
1018
+ paths.join(userProfile, "scoop", "shims"),
1019
+ ...chocolatey === void 0 ? [] : [paths.join(chocolatey, "bin")],
1020
+ paths.join(programData, "chocolatey", "bin"),
1021
+ ...typeSpecific[type]
1022
+ ];
1023
+ }
1024
+ function formulaPattern(type) {
1025
+ switch (type) {
1026
+ case "mysql": return /^(?:mysql|mysql-client)(?:@.+)?$/i;
1027
+ case "postgres": return /^(?:postgresql(?:@.+)?|libpq)$/i;
1028
+ case "sqlite": return /^sqlite(?:@.+)?$/i;
1029
+ case "oracle": return /^(?:oracle|instantclient)(?:@.+)?$/i;
1030
+ case "hive": return /^hive(?:@.+)?$/i;
1031
+ case "impala": return /^impala(?:@.+)?$/i;
1032
+ }
1033
+ }
1034
+ function dynamicDirectories(type, system, paths) {
1035
+ const result = [];
1036
+ if (system.platform === "darwin") {
1037
+ const pattern = formulaPattern(type);
1038
+ result.push({
1039
+ root: "/opt/homebrew/opt",
1040
+ accepts: (name) => pattern.test(name),
1041
+ suffix: ["bin"]
1042
+ }, {
1043
+ root: "/usr/local/opt",
1044
+ accepts: (name) => pattern.test(name),
1045
+ suffix: ["bin"]
1046
+ });
1047
+ if (type === "postgres") result.push({
1048
+ root: "/Library/PostgreSQL",
1049
+ accepts: () => true,
1050
+ suffix: ["bin"]
1051
+ }, {
1052
+ root: "/Applications/Postgres.app/Contents/Versions",
1053
+ accepts: (name) => name !== "latest",
1054
+ suffix: ["bin"]
1055
+ });
1056
+ if (type === "oracle") result.push({
1057
+ root: "/opt/oracle",
1058
+ accepts: (name) => /^instantclient/i.test(name),
1059
+ suffix: []
1060
+ });
1061
+ } else if (system.platform === "linux") {
1062
+ const pattern = formulaPattern(type);
1063
+ result.push({
1064
+ root: "/home/linuxbrew/.linuxbrew/opt",
1065
+ accepts: (name) => pattern.test(name),
1066
+ suffix: ["bin"]
1067
+ }, {
1068
+ root: paths.join(system.homeDir, ".linuxbrew", "opt"),
1069
+ accepts: (name) => pattern.test(name),
1070
+ suffix: ["bin"]
1071
+ });
1072
+ } else if (system.platform === "win32") {
1073
+ const roots = [environmentValue(system.env, "ProgramFiles", system.platform) ?? "C:\\Program Files", environmentValue(system.env, "ProgramFiles(x86)", system.platform) ?? "C:\\Program Files (x86)"];
1074
+ for (const root of roots) if (type === "mysql") result.push({
1075
+ root: paths.join(root, "MySQL"),
1076
+ accepts: () => true,
1077
+ suffix: ["bin"]
1078
+ }, {
1079
+ root,
1080
+ accepts: (name) => /^MariaDB/i.test(name),
1081
+ suffix: ["bin"]
1082
+ });
1083
+ else if (type === "postgres") result.push({
1084
+ root: paths.join(root, "PostgreSQL"),
1085
+ accepts: () => true,
1086
+ suffix: ["bin"]
1087
+ });
1088
+ else if (type === "oracle") result.push({
1089
+ root: paths.join(root, "Oracle"),
1090
+ accepts: () => true,
1091
+ suffix: ["bin"]
1092
+ });
1093
+ }
1094
+ return result;
1095
+ }
1096
+ async function expandDynamicDirectories(descriptors, system, paths, signal) {
1097
+ return (await Promise.all(descriptors.map(async (descriptor) => {
1098
+ signal.throwIfAborted();
1099
+ let names;
1100
+ try {
1101
+ names = await system.readDirectory(descriptor.root);
1102
+ } catch {
1103
+ return [];
1104
+ }
1105
+ signal.throwIfAborted();
1106
+ return names.filter((name) => descriptor.accepts(name)).sort((left, right) => right.localeCompare(left, void 0, {
1107
+ numeric: true,
1108
+ sensitivity: "base"
1109
+ })).slice(0, MAX_DYNAMIC_ENTRIES).map((name) => paths.join(descriptor.root, name, ...descriptor.suffix));
1110
+ }))).flat();
1111
+ }
1112
+ /** Build ordered fallback directories without recursively scanning the host. */
1113
+ async function buildClientSearchDirectories(type, config, signal, system = DEFAULT_SYSTEM) {
1114
+ const paths = pathApi(system.platform);
1115
+ const configured = config?.searchPaths ?? [];
1116
+ const homes = clientHomeDirectories(type, system, paths);
1117
+ const fixed = system.platform === "win32" ? windowsFixedDirectories(type, system, paths) : system.platform === "darwin" ? macFixedDirectories(type) : linuxFixedDirectories(system, paths);
1118
+ const dynamic = await expandDynamicDirectories(dynamicDirectories(type, system, paths), system, paths, signal);
1119
+ signal.throwIfAborted();
1120
+ return normalizeDirectories([
1121
+ ...configured,
1122
+ ...homes,
1123
+ ...fixed,
1124
+ ...dynamic
1125
+ ], system, paths);
1126
+ }
1127
+ function hasPathSeparator(command) {
1128
+ return command.includes("/") || command.includes("\\");
1129
+ }
1130
+ function withSearchPath(explicitEnv, directories, system) {
1131
+ const pathName = system.platform === "win32" ? Object.keys(system.env).find((name) => name.toLowerCase() === "path") ?? "Path" : "PATH";
1132
+ const explicitPathName = Object.keys(explicitEnv).find((name) => system.platform === "win32" ? name.toLowerCase() === "path" : name === "PATH");
1133
+ const parentPath = explicitPathName === void 0 ? environmentValue(system.env, "PATH", system.platform) : explicitEnv[explicitPathName];
1134
+ const separator = system.platform === "win32" ? ";" : ":";
1135
+ const prefix = directories.join(separator);
1136
+ const combined = parentPath === void 0 || parentPath === "" ? prefix : `${prefix}${separator}${parentPath}`;
1137
+ const result = { ...explicitEnv };
1138
+ if (explicitPathName !== void 0 && explicitPathName !== pathName) delete result[explicitPathName];
1139
+ result[pathName] = combined;
1140
+ return result;
1141
+ }
1142
+ function errorText(error) {
1143
+ return error instanceof Error ? error.message : String(error);
1144
+ }
1145
+ function checkedDirectoriesText(directories) {
1146
+ const visible = directories.slice(0, 16);
1147
+ const suffix = directories.length > visible.length ? `,另有${directories.length - visible.length}个目录` : "";
1148
+ return visible.length === 0 ? "无补充目录" : `${visible.join("、")}${suffix}`;
1149
+ }
1150
+ /**
1151
+ * Resolve one configured/default client. Current PATH (or an explicit path)
1152
+ * always wins. Only a missing bare command activates bounded PATH discovery.
1153
+ */
1154
+ async function resolveClientExecutable(options) {
1155
+ const system = options.system ?? DEFAULT_SYSTEM;
1156
+ let initialError;
1157
+ try {
1158
+ return {
1159
+ executable: await options.resolveExecutable(options.command, options.env, options.signal),
1160
+ env: options.env,
1161
+ searchedDirectories: []
1162
+ };
1163
+ } catch (error) {
1164
+ options.signal.throwIfAborted();
1165
+ initialError = error;
1166
+ }
1167
+ if (pathApi(system.platform).isAbsolute(options.command) || hasPathSeparator(options.command)) throw new Error(`无法解析数据库客户端 "${options.command}"(类型 ${options.type}:${errorText(initialError)});该显式路径不会回退到默认命令,请检查 clients.${options.type}.command`);
1168
+ const directories = await buildClientSearchDirectories(options.type, options.config, options.signal, system);
1169
+ const discoveryEnv = withSearchPath(options.env, directories, system);
1170
+ try {
1171
+ return {
1172
+ executable: await options.resolveExecutable(options.command, discoveryEnv, options.signal),
1173
+ env: discoveryEnv,
1174
+ searchedDirectories: directories
1175
+ };
1176
+ } catch (fallbackError) {
1177
+ options.signal.throwIfAborted();
1178
+ throw new Error(`无法解析数据库客户端 "${options.command}"(类型 ${options.type};当前PATH:${errorText(initialError)};补充PATH:${errorText(fallbackError)})。已检查:${checkedDirectoriesText(directories)};请确认客户端已安装,或配置 clients.${options.type}.command / clients.${options.type}.searchPaths`);
1179
+ }
1180
+ }
1181
+ //#endregion
894
1182
  //#region src/query.ts
895
1183
  /** Read one collected stream from offset 0. */
896
1184
  function readCaptured(reader) {
@@ -934,15 +1222,16 @@ async function runClientQuery(ctx, connection, sql, options, externalSignal, int
934
1222
  if (externalSignal.aborted) controller.abort(externalSignal.reason);
935
1223
  else externalSignal.addEventListener("abort", onExternalAbort, { once: true });
936
1224
  try {
937
- let executable;
938
- try {
939
- executable = await ctx.subprocess.resolveExecutable(template.command, template.env, controller.signal);
940
- } catch (error) {
941
- controller.signal.throwIfAborted();
942
- throw new Error(`无法解析数据库客户端 "${template.command}"(${error instanceof Error ? error.message : String(error)});请确认客户端已安装,或在 data-agent 插件配置的 clients 中覆盖命令名/路径`);
943
- }
1225
+ const resolution = await resolveClientExecutable({
1226
+ type: connection.type,
1227
+ command: template.command,
1228
+ config: options.clients[connection.type],
1229
+ env: template.env,
1230
+ signal: controller.signal,
1231
+ resolveExecutable: ctx.subprocess.resolveExecutable.bind(ctx.subprocess)
1232
+ });
944
1233
  const handle = ctx.subprocess.spawn({
945
- argv: [executable, ...template.args],
1234
+ argv: [resolution.executable, ...template.args],
946
1235
  cwd: process.cwd(),
947
1236
  stdio: {
948
1237
  stdin: { data: `${template.stdinPrefix}${sql}\n` },
@@ -951,7 +1240,7 @@ async function runClientQuery(ctx, connection, sql, options, externalSignal, int
951
1240
  },
952
1241
  graceMs: options.graceMs ?? 5e3,
953
1242
  signal: controller.signal,
954
- env: template.env
1243
+ env: resolution.env
955
1244
  });
956
1245
  let outcome;
957
1246
  try {
@@ -1016,7 +1305,8 @@ function normalizeConnectionInput(input, cwd = process.cwd()) {
1016
1305
  if (input.name !== void 0 && input.name.trim().length === 0) throw new Error("name 不能为空");
1017
1306
  const connection = {
1018
1307
  type: input.type,
1019
- database: input.type === "sqlite" ? resolve(cwd, input.database) : input.database
1308
+ database: input.type === "sqlite" ? resolve(cwd, input.database) : input.database,
1309
+ credentialMode: input.type === "sqlite" ? "none" : input.passwordRef !== void 0 ? "reference" : input.password !== void 0 && input.password.length > 0 ? "password" : "none"
1020
1310
  };
1021
1311
  if (input.type !== "sqlite") {
1022
1312
  if (input.host !== void 0 && input.host.length > 0) connection.host = input.host;
@@ -1057,16 +1347,21 @@ function createConnectionService(ctx, options, persistence) {
1057
1347
  return ctx;
1058
1348
  };
1059
1349
  const resolveCredential = async (connection) => {
1060
- if (connection.passwordRef === void 0) return {
1061
- ...connection,
1062
- tables: copyTables(connection.tables)
1063
- };
1064
- const ref = validatedCredentialRef(connection.passwordRef);
1065
- const hit = await requireContext().credentials.resolve(ref);
1066
- if (hit === void 0 || hit.value.length === 0) throw new Error(`凭据引用 "${connection.passwordRef}" 未配置`);
1350
+ const mode = credentialModeOf(connection);
1351
+ if (mode === "reference") {
1352
+ if (connection.passwordRef === void 0) throw new Error("数据库凭据引用缺失,请重新配置连接");
1353
+ const ref = validatedCredentialRef(connection.passwordRef);
1354
+ const hit = await requireContext().credentials.resolve(ref);
1355
+ if (hit === void 0 || hit.value.length === 0) throw new Error(`凭据引用 "${connection.passwordRef}" 未配置`);
1356
+ return {
1357
+ ...connection,
1358
+ password: hit.value,
1359
+ tables: copyTables(connection.tables)
1360
+ };
1361
+ }
1362
+ if (mode === "password" && connection.password === void 0) throw new Error("数据库凭据需要重新输入;请打开数据库配置并重新连接");
1067
1363
  return {
1068
1364
  ...connection,
1069
- password: hit.value,
1070
1365
  tables: copyTables(connection.tables)
1071
1366
  };
1072
1367
  };
@@ -1111,18 +1406,29 @@ function createConnectionService(ctx, options, persistence) {
1111
1406
  }
1112
1407
  };
1113
1408
  const credentialSummary = async (connection) => {
1114
- if (connection.type === "sqlite") return void 0;
1115
- if (connection.password !== void 0) return {
1409
+ const mode = credentialModeOf(connection);
1410
+ if (connection.type === "sqlite" || mode === "none") return void 0;
1411
+ if (mode === "password") return connection.password === void 0 ? { configured: false } : {
1116
1412
  configured: true,
1117
1413
  source: "memory"
1118
1414
  };
1119
- if (connection.passwordRef === void 0) return { configured: false };
1415
+ if (mode !== "reference" || connection.passwordRef === void 0) return { configured: false };
1120
1416
  const info = await requireContext().credentials.describe(validatedCredentialRef(connection.passwordRef));
1121
1417
  return {
1122
1418
  configured: info.configured,
1123
1419
  ...info.source !== void 0 ? { source: info.source } : {}
1124
1420
  };
1125
1421
  };
1422
+ const statusSummary = async (connection) => {
1423
+ const summary = summarize(connection);
1424
+ const mode = credentialModeOf(connection);
1425
+ summary.credentialMode = mode;
1426
+ summary.credential = await credentialSummary(connection);
1427
+ const ready = mode === "none" || summary.credential?.configured === true;
1428
+ summary.ready = ready;
1429
+ summary.reconnectRequired = !ready;
1430
+ return summary;
1431
+ };
1126
1432
  const service = {
1127
1433
  set(sessionId, connection) {
1128
1434
  if (connection.password !== void 0 && connection.passwordRef !== void 0) throw new Error("password 与 passwordRef 不能同时提供");
@@ -1165,9 +1471,7 @@ function createConnectionService(ctx, options, persistence) {
1165
1471
  async status(sessionId) {
1166
1472
  const connection = rawConnection(sessionId);
1167
1473
  if (connection === void 0) return void 0;
1168
- const summary = summarize(connection);
1169
- summary.credential = await credentialSummary(connection);
1170
- return summary;
1474
+ return statusSummary(connection);
1171
1475
  },
1172
1476
  async connect(sessionId, input, signal) {
1173
1477
  if (sessionId.length === 0) throw new Error("sessionId 必须是非空字符串");
@@ -1183,11 +1487,9 @@ function createConnectionService(ctx, options, persistence) {
1183
1487
  tables
1184
1488
  };
1185
1489
  runtime.set(sessionId, published);
1186
- const summary = summarize(published);
1187
- summary.credential = await credentialSummary(published);
1188
1490
  return {
1189
1491
  tables,
1190
- summary
1492
+ summary: await statusSummary(published)
1191
1493
  };
1192
1494
  },
1193
1495
  async disconnect(sessionId) {
@@ -1202,11 +1504,9 @@ function createConnectionService(ctx, options, persistence) {
1202
1504
  tables
1203
1505
  };
1204
1506
  runtime.set(sessionId, published);
1205
- const summary = summarize(published);
1206
- summary.credential = await credentialSummary(published);
1207
1507
  return {
1208
1508
  tables,
1209
- summary
1509
+ summary: await statusSummary(published)
1210
1510
  };
1211
1511
  },
1212
1512
  async resolveForExecution(sessionId) {
@@ -1294,7 +1594,8 @@ function connectionFromProfile(profileId, profile) {
1294
1594
  ...profile.port !== void 0 ? { port: profile.port } : {},
1295
1595
  ...profile.user !== void 0 ? { user: profile.user } : {},
1296
1596
  ...profile.readonly !== void 0 ? { readonly: profile.readonly } : {},
1297
- ...profile.passwordRef !== void 0 ? { passwordRef: profile.passwordRef } : {}
1597
+ ...profile.passwordRef !== void 0 ? { passwordRef: profile.passwordRef } : {},
1598
+ credentialMode: profile.credentialMode ?? (profile.type === "sqlite" ? "none" : profile.passwordRef !== void 0 ? "reference" : "password")
1298
1599
  };
1299
1600
  }
1300
1601
  function profileFromConnection(connection, updatedAt) {
@@ -1307,9 +1608,18 @@ function profileFromConnection(connection, updatedAt) {
1307
1608
  ...connection.port !== void 0 ? { port: connection.port } : {},
1308
1609
  ...connection.user !== void 0 ? { user: connection.user } : {},
1309
1610
  ...connection.readonly !== void 0 ? { readonly: connection.readonly } : {},
1310
- ...connection.passwordRef !== void 0 ? { passwordRef: connection.passwordRef } : {}
1611
+ ...connection.passwordRef !== void 0 ? { passwordRef: connection.passwordRef } : {},
1612
+ ...connection.credentialMode !== void 0 ? { credentialMode: connection.credentialMode } : {}
1311
1613
  };
1312
1614
  }
1615
+ /** Infer legacy records while leaving ambiguous secret-less SQL profiles conservative. */
1616
+ function credentialModeOf(connection) {
1617
+ if (connection.credentialMode !== void 0) return connection.credentialMode;
1618
+ if (connection.type === "sqlite") return "none";
1619
+ if (connection.passwordRef !== void 0) return "reference";
1620
+ if (connection.password !== void 0) return "password";
1621
+ return "none";
1622
+ }
1313
1623
  function requireIdentifier(type, value, label) {
1314
1624
  if (value === void 0 || value.length === 0) throw new Error(`${label} 不能为空`);
1315
1625
  sanitizeIdentifier(type, value);