@yejiming/dsh-data-agent 0.0.12 → 0.0.13

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 (46) hide show
  1. package/README.en.md +56 -10
  2. package/README.md +56 -10
  3. package/conformance/dsh-ecosystem/baseline.json +59 -0
  4. package/conformance/dsh-ecosystem/dependencies.json +41 -0
  5. package/conformance/dsh-ecosystem/fixtures/host-degraded.fixture.json +12 -0
  6. package/conformance/dsh-ecosystem/fixtures/host-eligible.fixture.json +13 -0
  7. package/conformance/dsh-ecosystem/fixtures/host-rejected.fixture.json +12 -0
  8. package/conformance/dsh-ecosystem/fixtures/profiles/native-only/package.json +8 -0
  9. package/conformance/dsh-ecosystem/fixtures/profiles/native-plus-adapter/package.json +9 -0
  10. package/conformance/dsh-ecosystem/inventory.json +77 -0
  11. package/conformance/dsh-ecosystem/restrictions.json +20 -0
  12. package/dsh-plugin.json +67 -0
  13. package/lib/client.js +1348 -129
  14. package/lib/client.js.map +1 -1
  15. package/lib/{command-DuCpwVbl.js → command-utC5MHd9.js} +101 -60
  16. package/lib/command.js +1 -1
  17. package/lib/{connections-5sfdEDsG.js → connections-CHY4uB6z.js} +747 -65
  18. package/lib/defaults-Cngd8Tf8.js +131 -0
  19. package/lib/ecosystem.js +19 -0
  20. package/lib/index.js +30 -22
  21. package/lib/routes.js +5 -6
  22. package/lib/{tool-DgL0fBfj.js → tool-ZTOS4B33.js} +5 -167
  23. package/lib/tool.js +1 -1
  24. package/lib/types/client/DataAgentWorkbench.d.ts +1 -2
  25. package/lib/types/client/QueryResultTable.d.ts +13 -0
  26. package/lib/types/client/locales.d.ts +48 -0
  27. package/lib/types/client/persistence.d.ts +3 -1
  28. package/lib/types/client/query-export.d.ts +11 -0
  29. package/lib/types/client-discovery.d.ts +3 -4
  30. package/lib/types/clients.d.ts +14 -8
  31. package/lib/types/command.d.ts +2 -2
  32. package/lib/types/connections.d.ts +33 -4
  33. package/lib/types/database-types.d.ts +23 -0
  34. package/lib/types/defaults.d.ts +4 -0
  35. package/lib/types/ecosystem.d.ts +13 -0
  36. package/lib/types/index.d.ts +16 -10
  37. package/lib/types/query.d.ts +13 -11
  38. package/lib/types/sql.d.ts +9 -1
  39. package/lib/types/storage.d.ts +11 -1
  40. package/lib/types/structured-read.d.ts +2 -2
  41. package/lib/types/structured.d.ts +1 -1
  42. package/lib/types/tool.d.ts +5 -5
  43. package/lib/types/tui-connection-form.d.ts +10 -7
  44. package/package.json +27 -3
  45. package/preset/data-agent/agent.cordis.yml +4 -1
  46. package/lib/defaults-DP4RyRh1.js +0 -21
@@ -0,0 +1,131 @@
1
+ //#region src/database-types.ts
2
+ /**
3
+ * Browser-safe database type descriptors shared by every DSH surface.
4
+ * Keep this module dependency-free: server-only client/process details belong
5
+ * in the database adapters, not in Web or persistence bundles.
6
+ */
7
+ const DATABASE_TYPES = [
8
+ "mysql",
9
+ "postgres",
10
+ "sqlite",
11
+ "oracle",
12
+ "hive",
13
+ "impala",
14
+ "clickhouse",
15
+ "doris",
16
+ "sqlserver"
17
+ ];
18
+ const DATABASE_TYPE_DESCRIPTORS = {
19
+ mysql: {
20
+ type: "mysql",
21
+ label: "MySQL",
22
+ localeKey: "type.mysql",
23
+ defaultPort: 3306,
24
+ defaultUser: "root",
25
+ fileBased: false
26
+ },
27
+ postgres: {
28
+ type: "postgres",
29
+ label: "PostgreSQL",
30
+ localeKey: "type.postgres",
31
+ defaultPort: 5432,
32
+ defaultUser: "postgres",
33
+ fileBased: false
34
+ },
35
+ sqlite: {
36
+ type: "sqlite",
37
+ label: "SQLite",
38
+ localeKey: "type.sqlite",
39
+ defaultPort: 0,
40
+ defaultUser: "",
41
+ fileBased: true
42
+ },
43
+ oracle: {
44
+ type: "oracle",
45
+ label: "Oracle",
46
+ localeKey: "type.oracle",
47
+ defaultPort: 1521,
48
+ defaultUser: "",
49
+ fileBased: false
50
+ },
51
+ hive: {
52
+ type: "hive",
53
+ label: "Hive",
54
+ localeKey: "type.hive",
55
+ defaultPort: 1e4,
56
+ defaultUser: "",
57
+ fileBased: false
58
+ },
59
+ impala: {
60
+ type: "impala",
61
+ label: "Impala",
62
+ localeKey: "type.impala",
63
+ defaultPort: 21050,
64
+ defaultUser: "",
65
+ fileBased: false
66
+ },
67
+ clickhouse: {
68
+ type: "clickhouse",
69
+ label: "ClickHouse",
70
+ localeKey: "type.clickhouse",
71
+ defaultPort: 8123,
72
+ securePort: 8443,
73
+ defaultUser: "default",
74
+ fileBased: false
75
+ },
76
+ doris: {
77
+ type: "doris",
78
+ label: "Apache Doris",
79
+ localeKey: "type.doris",
80
+ defaultPort: 9030,
81
+ defaultUser: "root",
82
+ fileBased: false
83
+ },
84
+ sqlserver: {
85
+ type: "sqlserver",
86
+ label: "SQL Server",
87
+ localeKey: "type.sqlserver",
88
+ defaultPort: 1433,
89
+ defaultUser: "sa",
90
+ fileBased: false
91
+ }
92
+ };
93
+ function isDatabaseType(value) {
94
+ return typeof value === "string" && DATABASE_TYPES.includes(value);
95
+ }
96
+ function defaultDatabasePort(type, secure = false) {
97
+ const descriptor = DATABASE_TYPE_DESCRIPTORS[type];
98
+ return secure && descriptor.securePort !== void 0 ? descriptor.securePort : descriptor.defaultPort;
99
+ }
100
+ function defaultDatabaseUser(type) {
101
+ return DATABASE_TYPE_DESCRIPTORS[type].defaultUser;
102
+ }
103
+ function databaseTypeLabel(type) {
104
+ return DATABASE_TYPE_DESCRIPTORS[type].label;
105
+ }
106
+ //#endregion
107
+ //#region src/defaults.ts
108
+ /**
109
+ * Package-wide defaults shared by the server half (`src/index.ts`) and the
110
+ * database tool half (`src/tool.ts`). Loader schemas carry these as their
111
+ * defaults so a deployment may override every one of them in cordis.yml.
112
+ * @module @yejiming/dsh-data-agent/defaults
113
+ */
114
+ /** Preset directory name installed into `$DSH_HOME/.agent-presets/`. */
115
+ const DEFAULT_PRESET_ID = "data-agent";
116
+ /** End-to-end deadline for one `/connect` connectivity check, milliseconds. */
117
+ const DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
118
+ /** End-to-end deadline for one database-tool query, milliseconds. */
119
+ const DEFAULT_QUERY_TIMEOUT_MS = 3e4;
120
+ /** In-memory cap on database-tool captured output (stdout and stderr each). */
121
+ const DEFAULT_MAX_RESULT_CHARS = 2e4;
122
+ /** Hard row cap for one structured Web workbench result/export. */
123
+ const WORKBENCH_MAX_EXPORT_ROWS = 5e4;
124
+ /** Bounded capture size for the larger structured Web workbench result. */
125
+ const WORKBENCH_MAX_RESULT_CHARS = 33554432;
126
+ /** Cap on one /query SQL text length (abuse guard; the wire body stays small). */
127
+ const DEFAULT_MAX_QUERY_CHARS = 65536;
128
+ /** Grace period for the subprocess terminate escalation. */
129
+ const DEFAULT_GRACE_MS = 5e3;
130
+ //#endregion
131
+ export { DEFAULT_PRESET_ID as a, WORKBENCH_MAX_RESULT_CHARS as c, defaultDatabasePort as d, defaultDatabaseUser as f, DEFAULT_MAX_RESULT_CHARS as i, DATABASE_TYPES as l, DEFAULT_GRACE_MS as n, DEFAULT_QUERY_TIMEOUT_MS as o, isDatabaseType as p, DEFAULT_MAX_QUERY_CHARS as r, WORKBENCH_MAX_EXPORT_ROWS as s, DEFAULT_CONNECT_TIMEOUT_MS as t, databaseTypeLabel as u };
@@ -0,0 +1,19 @@
1
+ import { defineFacet } from "@dsh-std/sdk";
2
+ //#region src/ecosystem.ts
3
+ /**
4
+ * Community v0.15 host facet.
5
+ *
6
+ * This facet intentionally publishes no commands, tools, UI, routes, storage,
7
+ * credentials, or database effects. The existing Cordis bundle remains the
8
+ * sole functional runtime so hosts may discover the ecosystem declaration
9
+ * without double-registering data-agent behavior.
10
+ */
11
+ /** Stable degraded snapshot for declaration-only ecosystem discovery. */
12
+ const ECOSYSTEM_SNAPSHOT = Object.freeze({
13
+ state: "degraded",
14
+ message: "Native Cordis runtime owns all data-agent effects; this facet publishes declarations only.",
15
+ extensions: Object.freeze([])
16
+ });
17
+ const ecosystemFacet = defineFacet(() => void 0, () => void 0, () => ECOSYSTEM_SNAPSHOT);
18
+ //#endregion
19
+ export { ECOSYSTEM_SNAPSHOT, ecosystemFacet as default };
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { o as clientsSchema, t as createConnectionService } from "./connections-5sfdEDsG.js";
2
- import { a as DEFAULT_PRESET_ID, i as DEFAULT_MAX_RESULT_CHARS, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-DP4RyRh1.js";
3
- import { r as apply$1 } from "./command-DuCpwVbl.js";
4
- import { n as apply$2 } from "./tool-DgL0fBfj.js";
1
+ import { a as DEFAULT_PRESET_ID, i as DEFAULT_MAX_RESULT_CHARS, l as DATABASE_TYPES, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-Cngd8Tf8.js";
2
+ import { c as clientsSchema, t as createConnectionService } from "./connections-CHY4uB6z.js";
3
+ import { r as apply$1 } from "./command-utC5MHd9.js";
4
+ import { n as apply$2 } from "./tool-ZTOS4B33.js";
5
5
  import { createHash } from "node:crypto";
6
6
  import { access, cp, mkdir, readFile, writeFile } from "node:fs/promises";
7
7
  import { homedir } from "node:os";
@@ -29,19 +29,13 @@ const CONNECTION_STORAGE_DOMAIN = "data_agent_connections";
29
29
  /** Durable profile schema. There is deliberately no `password` field. */
30
30
  const persistedConnectionProfileSchema = z$1.object({
31
31
  name: z$1.string().min(1).optional(),
32
- type: z$1.enum([
33
- "mysql",
34
- "postgres",
35
- "sqlite",
36
- "oracle",
37
- "hive",
38
- "impala"
39
- ]),
32
+ type: z$1.enum(DATABASE_TYPES),
40
33
  host: z$1.string().optional(),
41
34
  port: z$1.number().int().min(1).max(65535).optional(),
42
35
  user: z$1.string().optional(),
43
36
  database: z$1.string().min(1),
44
37
  readonly: z$1.boolean().optional(),
38
+ secure: z$1.boolean().optional(),
45
39
  passwordRef: z$1.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional(),
46
40
  credentialMode: z$1.enum([
47
41
  "none",
@@ -57,19 +51,13 @@ const sessionConnectionBindingSchema = z$1.object({
57
51
  }).strict();
58
52
  /** Session form draft schema. Secret-shaped fields are rejected by strict mode. */
59
53
  const persistedConnectionFormDraftSchema = z$1.object({
60
- type: z$1.enum([
61
- "mysql",
62
- "postgres",
63
- "sqlite",
64
- "oracle",
65
- "hive",
66
- "impala"
67
- ]),
54
+ type: z$1.enum(DATABASE_TYPES),
68
55
  host: z$1.string(),
69
56
  port: z$1.string(),
70
57
  user: z$1.string(),
71
58
  database: z$1.string(),
72
59
  readonly: z$1.boolean(),
60
+ secure: z$1.boolean().optional(),
73
61
  updatedAt: z$1.string().min(1)
74
62
  }).strict();
75
63
  /** Single source of truth for the storage layout and durable validation. */
@@ -82,6 +70,15 @@ const connectionStorageSpec = defineDomain({
82
70
  drafts: domainTable(persistedConnectionFormDraftSchema)
83
71
  }
84
72
  });
73
+ /** Select the newest successful profile with a deterministic id tie-break. */
74
+ function latestConnectionProfile(entries) {
75
+ let latest;
76
+ for (const [profileId, profile] of entries) if (latest === void 0 || profile.updatedAt > latest.profile.updatedAt || profile.updatedAt === latest.profile.updatedAt && profileId > latest.profileId) latest = {
77
+ profileId,
78
+ profile
79
+ };
80
+ return latest;
81
+ }
85
82
  /** Project a typed DSH domain handle onto the service's persistence seam. */
86
83
  function createDomainConnectionPersistence(domain) {
87
84
  const profiles = domain.table("profiles");
@@ -91,6 +88,9 @@ function createDomainConnectionPersistence(domain) {
91
88
  getProfile(profileId) {
92
89
  return profiles.get(profileId);
93
90
  },
91
+ getLatestProfile() {
92
+ return latestConnectionProfile(profiles.entries());
93
+ },
94
94
  putProfile(profileId, profile) {
95
95
  return profiles.put(profileId, profile);
96
96
  },
@@ -111,6 +111,9 @@ function createDomainConnectionPersistence(domain) {
111
111
  },
112
112
  putDraft(sessionId, draft) {
113
113
  return drafts.put(sessionId, draft);
114
+ },
115
+ deleteDraft(sessionId) {
116
+ return drafts.delete(sessionId);
114
117
  }
115
118
  };
116
119
  }
@@ -163,13 +166,17 @@ const Config = z.object({
163
166
  z.const("sqlite"),
164
167
  z.const("oracle"),
165
168
  z.const("hive"),
166
- z.const("impala")
169
+ z.const("impala"),
170
+ z.const("clickhouse"),
171
+ z.const("doris"),
172
+ z.const("sqlserver")
167
173
  ]),
168
174
  host: z.string(),
169
175
  port: z.natural(),
170
176
  user: z.string(),
171
177
  database: z.string(),
172
178
  readonly: z.boolean(),
179
+ secure: z.boolean(),
173
180
  passwordRef: z.string().pattern(/^[A-Za-z_][A-Za-z0-9_]*$/),
174
181
  password: z.never().hidden()
175
182
  })).default({})
@@ -306,7 +313,8 @@ async function apply(ctx, config) {
306
313
  ...spec.port !== void 0 ? { port: spec.port } : {},
307
314
  ...spec.user !== void 0 ? { user: spec.user } : {},
308
315
  ...spec.passwordRef !== void 0 ? { passwordRef: spec.passwordRef } : {},
309
- ...spec.readonly !== void 0 ? { readonly: spec.readonly } : {}
316
+ ...spec.readonly !== void 0 ? { readonly: spec.readonly } : {},
317
+ ...spec.secure !== void 0 ? { secure: spec.secure } : {}
310
318
  };
311
319
  store.set(sessionId, connection);
312
320
  }
package/lib/routes.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as DEFAULT_MAX_RESULT_CHARS, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-DP4RyRh1.js";
1
+ import { i as DEFAULT_MAX_RESULT_CHARS, l as DATABASE_TYPES, o as DEFAULT_QUERY_TIMEOUT_MS, p as isDatabaseType, r as DEFAULT_MAX_QUERY_CHARS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-Cngd8Tf8.js";
2
2
  import { resolve } from "node:path";
3
3
  import z from "schemastery";
4
4
  //#region src/routes.ts
@@ -20,12 +20,13 @@ function validateConnectBody(value, cwd = process.cwd()) {
20
20
  const candidate = value;
21
21
  const sessionId = requireString(candidate.sessionId, "sessionId");
22
22
  const type = candidate.type;
23
- if (!isDatabaseType(type)) throw new Error("type 必须是 \"mysql\"、\"postgres\"、\"sqlite\"、\"oracle\"、\"hive\" 或 \"impala\"");
23
+ if (!isDatabaseType(type)) throw new Error(`type 必须是受支持的数据库类型:${DATABASE_TYPES.join("")}`);
24
24
  const database = requireString(candidate.database, "database");
25
25
  const password = optionalString(candidate.password, "password");
26
26
  const passwordRef = optionalString(candidate.passwordRef, "passwordRef");
27
27
  if (password !== void 0 && passwordRef !== void 0) throw new Error("password 与 passwordRef 不能同时提供");
28
28
  const readonly = optionalBoolean(candidate.readonly, "readonly");
29
+ const secure = optionalBoolean(candidate.secure, "secure");
29
30
  const profileId = optionalString(candidate.profileId, "profileId");
30
31
  const profileName = optionalString(candidate.name, "name");
31
32
  const request = {
@@ -34,6 +35,7 @@ function validateConnectBody(value, cwd = process.cwd()) {
34
35
  database: type === "sqlite" ? resolve(cwd, database) : database
35
36
  };
36
37
  if (readonly !== void 0) request.readonly = readonly;
38
+ if (type === "clickhouse" && secure !== void 0) request.secure = secure;
37
39
  if (profileId !== void 0) request.profileId = profileId;
38
40
  if (profileName !== void 0) request.name = profileName;
39
41
  if (type === "sqlite") return request;
@@ -133,7 +135,7 @@ function apply(ctx, _config) {
133
135
  const sql = requireString(body.sql, "sql");
134
136
  writeJson(200, {
135
137
  ok: true,
136
- result: await scope.dataAgentConnections.query(sessionId, sql, signal)
138
+ result: await scope.dataAgentConnections.executeInteractive(sessionId, sql, signal)
137
139
  });
138
140
  return;
139
141
  }
@@ -177,8 +179,5 @@ function optionalBoolean(value, label) {
177
179
  if (typeof value !== "boolean") throw new Error(`${label} 必须是布尔值`);
178
180
  return value;
179
181
  }
180
- function isDatabaseType(value) {
181
- return value === "mysql" || value === "postgres" || value === "sqlite" || value === "oracle" || value === "hive" || value === "impala";
182
- }
183
182
  //#endregion
184
183
  export { Config, DATA_AGENT_PATH, apply, inject, name, validateConnectBody };
@@ -1,5 +1,5 @@
1
- import { a as classifyStatement, c as assertSingleStatement, i as runClientQuery, n as redactQueryResult, o as clientsSchema, r as redactSecretText, s as enforceReadRowLimit } from "./connections-5sfdEDsG.js";
2
- import { i as DEFAULT_MAX_RESULT_CHARS, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS } from "./defaults-DP4RyRh1.js";
1
+ import { i as DEFAULT_MAX_RESULT_CHARS, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS } from "./defaults-Cngd8Tf8.js";
2
+ import { a as parseStructuredQueryOutput, c as clientsSchema, l as enforceReadRowLimit, n as redactQueryResult, o as runClientQuery, r as redactSecretText, s as classifyStatement, u as assertSingleStatement } from "./connections-CHY4uB6z.js";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { link, mkdir, unlink, writeFile } from "node:fs/promises";
5
5
  import { resolve } from "node:path";
@@ -607,168 +607,6 @@ const ANALYSIS_REPORT_OUTPUT_SCHEMA = {
607
607
  additionalProperties: false
608
608
  };
609
609
  //#endregion
610
- //#region src/structured.ts
611
- function normalizeNewlines(text) {
612
- return text.replace(/\r\n?/g, "\n");
613
- }
614
- function splitLine(line, delimiter) {
615
- return line.split(delimiter);
616
- }
617
- /** Make column names valid unique JSON object keys. */
618
- function uniqueColumns(columns) {
619
- const used = /* @__PURE__ */ new Set();
620
- return columns.map((raw, index) => {
621
- let name = raw.trim();
622
- if (name.length === 0) name = `column_${index + 1}`;
623
- if (used.has(name)) {
624
- let suffix = 2;
625
- while (used.has(`${name}_${suffix}`)) suffix += 1;
626
- name = `${name}_${suffix}`;
627
- }
628
- used.add(name);
629
- return name;
630
- });
631
- }
632
- function rowObject(columns, fields) {
633
- const row = {};
634
- for (let index = 0; index < columns.length; index += 1) row[columns[index]] = fields[index] ?? null;
635
- return row;
636
- }
637
- function emptyOutput() {
638
- return {
639
- columns: [],
640
- rows: [],
641
- rowLimitExceeded: false
642
- };
643
- }
644
- function skipLeadingBlank(lines) {
645
- let index = 0;
646
- while (index < lines.length && lines[index].trim().length === 0) index += 1;
647
- return index;
648
- }
649
- /** PostgreSQL `-A` appends a `(N rows)` / `(N row)` footer after SELECT output. */
650
- function isPostgresFooter(line) {
651
- return /^\(\d+ rows?\)$/.test(line.trim());
652
- }
653
- function parseDelimited(stdout, delimiter, maxRows, skipFooter = false) {
654
- const lines = normalizeNewlines(stdout).split("\n");
655
- if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
656
- const headerIndex = skipLeadingBlank(lines);
657
- if (headerIndex >= lines.length) return emptyOutput();
658
- const columns = uniqueColumns(splitLine(lines[headerIndex], delimiter));
659
- const rows = [];
660
- let rowLimitExceeded = false;
661
- for (let index = headerIndex + 1; index < lines.length; index += 1) {
662
- const line = lines[index];
663
- if (skipFooter && isPostgresFooter(line)) continue;
664
- if (rows.length >= maxRows) {
665
- rowLimitExceeded = true;
666
- break;
667
- }
668
- rows.push(rowObject(columns, splitLine(line, delimiter)));
669
- }
670
- return {
671
- columns,
672
- rows,
673
- rowLimitExceeded
674
- };
675
- }
676
- /** Minimal RFC-4180-style parser for sqlite3 `-csv` output. */
677
- function parseCsv(text) {
678
- const records = [];
679
- let record = [];
680
- let field = "";
681
- let quoted = false;
682
- let index = 0;
683
- const pushField = () => {
684
- record.push(field);
685
- field = "";
686
- };
687
- const pushRecord = () => {
688
- pushField();
689
- records.push(record);
690
- record = [];
691
- };
692
- while (index < text.length) {
693
- const char = text[index];
694
- if (quoted) {
695
- if (char === "\"") {
696
- if (text[index + 1] === "\"") {
697
- field += "\"";
698
- index += 2;
699
- continue;
700
- }
701
- quoted = false;
702
- index += 1;
703
- continue;
704
- }
705
- field += char;
706
- index += 1;
707
- continue;
708
- }
709
- if (char === "\"" && field.length === 0) {
710
- quoted = true;
711
- index += 1;
712
- continue;
713
- }
714
- if (char === ",") {
715
- pushField();
716
- index += 1;
717
- continue;
718
- }
719
- if (char === "\n") {
720
- pushRecord();
721
- index += 1;
722
- continue;
723
- }
724
- if (char === "\r") {
725
- if (text[index + 1] === "\n") index += 1;
726
- pushRecord();
727
- index += 1;
728
- continue;
729
- }
730
- field += char;
731
- index += 1;
732
- }
733
- if (field.length > 0 || record.length > 0) pushRecord();
734
- return records;
735
- }
736
- function parseCsvOutput(stdout, maxRows) {
737
- const records = parseCsv(normalizeNewlines(stdout)).filter((record) => !(record.length === 1 && record[0] === ""));
738
- if (records.length === 0) return emptyOutput();
739
- const columns = uniqueColumns(records[0]);
740
- const rows = [];
741
- let rowLimitExceeded = false;
742
- for (let index = 1; index < records.length; index += 1) {
743
- if (rows.length >= maxRows) {
744
- rowLimitExceeded = true;
745
- break;
746
- }
747
- rows.push(rowObject(columns, records[index]));
748
- }
749
- return {
750
- columns,
751
- rows,
752
- rowLimitExceeded
753
- };
754
- }
755
- /**
756
- * Parse one database type's structured-query stdout. The matching template is
757
- * `buildStructuredQueryTemplate`: mysql tab-separated with a header, postgres
758
- * pipe-separated with a header and row-count footer, sqlite CSV with a header,
759
- * oracle pipe-separated with heading on, hive/impala tsv with a header.
760
- */
761
- function parseStructuredQueryOutput(type, stdout, maxRows) {
762
- switch (type) {
763
- case "mysql": return parseDelimited(stdout, " ", maxRows);
764
- case "postgres": return parseDelimited(stdout, "|", maxRows, true);
765
- case "sqlite": return parseCsvOutput(stdout, maxRows);
766
- case "oracle": return parseDelimited(stdout, "|", maxRows);
767
- case "hive":
768
- case "impala": return parseDelimited(stdout, " ", maxRows);
769
- }
770
- }
771
- //#endregion
772
610
  //#region src/structured-read.ts
773
611
  /** Look up the session connection, failing with the same message for every tool. */
774
612
  async function requireToolConnection(ctx, exec, toolName) {
@@ -803,7 +641,7 @@ function runnerOptions(resolved, mode) {
803
641
  /**
804
642
  * Execute one read-only SQL through the structured client template and parse
805
643
  * it into the canonical { columns, rows } shape, with maxRows enforced at both
806
- * the SQL level (LIMIT injection) and the parse level.
644
+ * the SQL level (dialect rewrite) and the parse level.
807
645
  */
808
646
  async function runStructuredReadQuery(ctx, connection, sql, resolved, toolName, signal) {
809
647
  if (sql.trim().length === 0) throw new Error(toolName + ": sql 不能为空");
@@ -1099,7 +937,7 @@ function apply(ctx, config) {
1099
937
  parameters: { sql: {
1100
938
  type: "string",
1101
939
  required: true,
1102
- description: "一条只读 SQL,如 \"SELECT * FROM orders LIMIT 5;\"、\"SHOW TABLES;\"、\"DESCRIBE users;\""
940
+ description: "一条符合当前数据库方言的只读 SQL,如 \"SELECT * FROM orders;\"、\"SHOW TABLES;\"、\"DESCRIBE users;\""
1103
941
  } },
1104
942
  output: {
1105
943
  schema: {
@@ -1221,7 +1059,7 @@ function apply(ctx, config) {
1221
1059
  parameters: { sql: {
1222
1060
  type: "string",
1223
1061
  required: true,
1224
- description: "一条 SQL 文本(或客户端命令),如 \"SHOW TABLES;\"、\"DESCRIBE users;\"、\"SELECT * FROM orders LIMIT 5;\""
1062
+ description: "一条符合当前数据库方言的 SQL 文本(或数据库命令),如 \"SHOW TABLES;\"、\"DESCRIBE users;\"、\"SELECT * FROM orders;\""
1225
1063
  } },
1226
1064
  output: {
1227
1065
  schema: {
package/lib/tool.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as name, n as apply, r as inject, t as Config } from "./tool-DgL0fBfj.js";
1
+ import { i as name, n as apply, r as inject, t as Config } from "./tool-ZTOS4B33.js";
2
2
  export { Config, apply, inject, name };
@@ -1,6 +1,5 @@
1
1
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
- /** Database kinds offered by the connection form. */
3
- export type DatabaseType = 'mysql' | 'postgres' | 'sqlite' | 'oracle' | 'hive' | 'impala';
2
+ export type { DatabaseType } from '../database-types.ts';
4
3
  /** The sessions-list slice the workbench needs (structural; avoids a runtime import). */
5
4
  export interface SessionListLike {
6
5
  byId: Record<string, {
@@ -0,0 +1,13 @@
1
+ import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
2
+ export interface StructuredWorkbenchResult {
3
+ kind: 'table';
4
+ columns: string[];
5
+ rows: Record<string, string | null>[];
6
+ elapsedMs: number;
7
+ truncated: boolean;
8
+ maxRows: number;
9
+ }
10
+ export declare function QueryResultTable({ result, t, }: {
11
+ result: StructuredWorkbenchResult;
12
+ t: TranslateNS<'data-agent'>;
13
+ }): import("react").JSX.Element;
@@ -13,6 +13,9 @@ export declare const zh: {
13
13
  'type.oracle': string;
14
14
  'type.hive': string;
15
15
  'type.impala': string;
16
+ 'type.clickhouse': string;
17
+ 'type.doris': string;
18
+ 'type.sqlserver': string;
16
19
  'form.host': string;
17
20
  'form.port': string;
18
21
  'form.user': string;
@@ -28,6 +31,8 @@ export declare const zh: {
28
31
  'form.rememberPassword.hint': string;
29
32
  'form.readonly': string;
30
33
  'form.readonly.hint': string;
34
+ 'form.secure': string;
35
+ 'form.secure.hint': string;
31
36
  'form.database': string;
32
37
  'form.database.oracle': string;
33
38
  'form.database.hive': string;
@@ -57,10 +62,29 @@ export declare const zh: {
57
62
  'wb.columns': string;
58
63
  'wb.sql': string;
59
64
  'wb.sql.placeholder': string;
65
+ 'wb.sql.placeholder.sqlserver': string;
60
66
  'wb.sql.run': string;
61
67
  'wb.sql.running': string;
62
68
  'wb.sql.shortcut': string;
63
69
  'wb.sql.empty': string;
70
+ 'wb.sql.result.summary': string;
71
+ 'wb.sql.result.capped': string;
72
+ 'wb.sql.result.outputTruncated': string;
73
+ 'wb.sql.result.table': string;
74
+ 'wb.sql.result.noRows': string;
75
+ 'wb.sql.command.done': string;
76
+ 'wb.sql.export.actions': string;
77
+ 'wb.sql.export.excel': string;
78
+ 'wb.sql.export.csv': string;
79
+ 'wb.sql.export.clipboard': string;
80
+ 'wb.sql.exporting': string;
81
+ 'wb.sql.copying': string;
82
+ 'wb.sql.export.done': string;
83
+ 'wb.sql.copy.done': string;
84
+ 'wb.sql.export.failed': string;
85
+ 'wb.sql.page.summary': string;
86
+ 'wb.sql.page.previous': string;
87
+ 'wb.sql.page.next': string;
64
88
  'wb.loading': string;
65
89
  'wb.empty': string;
66
90
  'wb.modal.title': string;
@@ -101,6 +125,9 @@ export declare const en: {
101
125
  'type.oracle': string;
102
126
  'type.hive': string;
103
127
  'type.impala': string;
128
+ 'type.clickhouse': string;
129
+ 'type.doris': string;
130
+ 'type.sqlserver': string;
104
131
  'form.host': string;
105
132
  'form.port': string;
106
133
  'form.user': string;
@@ -116,6 +143,8 @@ export declare const en: {
116
143
  'form.rememberPassword.hint': string;
117
144
  'form.readonly': string;
118
145
  'form.readonly.hint': string;
146
+ 'form.secure': string;
147
+ 'form.secure.hint': string;
119
148
  'form.database': string;
120
149
  'form.database.oracle': string;
121
150
  'form.database.hive': string;
@@ -145,10 +174,29 @@ export declare const en: {
145
174
  'wb.columns': string;
146
175
  'wb.sql': string;
147
176
  'wb.sql.placeholder': string;
177
+ 'wb.sql.placeholder.sqlserver': string;
148
178
  'wb.sql.run': string;
149
179
  'wb.sql.running': string;
150
180
  'wb.sql.shortcut': string;
151
181
  'wb.sql.empty': string;
182
+ 'wb.sql.result.summary': string;
183
+ 'wb.sql.result.capped': string;
184
+ 'wb.sql.result.outputTruncated': string;
185
+ 'wb.sql.result.table': string;
186
+ 'wb.sql.result.noRows': string;
187
+ 'wb.sql.command.done': string;
188
+ 'wb.sql.export.actions': string;
189
+ 'wb.sql.export.excel': string;
190
+ 'wb.sql.export.csv': string;
191
+ 'wb.sql.export.clipboard': string;
192
+ 'wb.sql.exporting': string;
193
+ 'wb.sql.copying': string;
194
+ 'wb.sql.export.done': string;
195
+ 'wb.sql.copy.done': string;
196
+ 'wb.sql.export.failed': string;
197
+ 'wb.sql.page.summary': string;
198
+ 'wb.sql.page.previous': string;
199
+ 'wb.sql.page.next': string;
152
200
  'wb.loading': string;
153
201
  'wb.empty': string;
154
202
  'wb.modal.title': string;
@@ -9,7 +9,7 @@
9
9
  * key is versioned so a future shape change can migrate or ignore old data.
10
10
  * @module @yejiming/dsh-data-agent/persistence
11
11
  */
12
- import type { DatabaseType } from './DataAgentWorkbench.tsx';
12
+ import { type DatabaseType } from '../database-types.ts';
13
13
  /** localStorage key holding the most recent connection configuration. */
14
14
  export declare const CONNECTION_STORAGE_KEY = "dsh-data-agent.connection.v1";
15
15
  /** The persisted connection configuration. */
@@ -28,6 +28,8 @@ export interface SavedConnection {
28
28
  /** Opt-in flag; when true, {@link saveConnection} may write `password`. */
29
29
  persistPassword?: boolean;
30
30
  readonly?: boolean;
31
+ /** ClickHouse only: HTTPS with normal certificate verification. */
32
+ secure?: boolean;
31
33
  /** Diagnostic timestamp of the save. */
32
34
  savedAt: string;
33
35
  }
@@ -0,0 +1,11 @@
1
+ /** One structured result shape accepted by every workbench export. */
2
+ export interface QueryExportData {
3
+ columns: readonly string[];
4
+ rows: readonly Readonly<Record<string, string | null>>[];
5
+ }
6
+ /** UTF-8 CSV with BOM so desktop Excel opens Chinese text correctly. */
7
+ export declare function queryResultToCsv(data: QueryExportData): string;
8
+ /** Tabular plain text suitable for spreadsheet clipboard paste. */
9
+ export declare function queryResultToTsv(data: QueryExportData): string;
10
+ /** Build a real XLSX workbook using inline strings, frozen headers, and filters. */
11
+ export declare function queryResultToXlsx(data: QueryExportData): Uint8Array;