@yejiming/dsh-data-agent 0.0.11 → 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 (49) hide show
  1. package/README.en.md +70 -15
  2. package/README.md +70 -15
  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 +1352 -130
  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 +40 -33
  21. package/lib/routes.js +5 -6
  22. package/lib/{tool-DVh61An-.js → tool-ZTOS4B33.js} +161 -177
  23. package/lib/tool.js +1 -1
  24. package/lib/types/analysis-html.d.ts +27 -0
  25. package/lib/types/analysis.d.ts +13 -1
  26. package/lib/types/client/DataAgentWorkbench.d.ts +1 -2
  27. package/lib/types/client/QueryResultTable.d.ts +13 -0
  28. package/lib/types/client/locales.d.ts +48 -0
  29. package/lib/types/client/persistence.d.ts +3 -1
  30. package/lib/types/client/query-export.d.ts +11 -0
  31. package/lib/types/client-discovery.d.ts +3 -4
  32. package/lib/types/clients.d.ts +14 -8
  33. package/lib/types/command.d.ts +2 -2
  34. package/lib/types/connections.d.ts +33 -4
  35. package/lib/types/database-types.d.ts +23 -0
  36. package/lib/types/defaults.d.ts +4 -0
  37. package/lib/types/ecosystem.d.ts +13 -0
  38. package/lib/types/index.d.ts +21 -16
  39. package/lib/types/presentation-text.d.ts +3 -0
  40. package/lib/types/query.d.ts +13 -11
  41. package/lib/types/sql.d.ts +9 -1
  42. package/lib/types/storage.d.ts +11 -1
  43. package/lib/types/structured-read.d.ts +2 -2
  44. package/lib/types/structured.d.ts +1 -1
  45. package/lib/types/tool.d.ts +5 -5
  46. package/lib/types/tui-connection-form.d.ts +10 -7
  47. package/package.json +66 -42
  48. package/preset/data-agent/agent.cordis.yml +9 -4
  49. 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-DVh61An-.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({})
@@ -186,12 +193,11 @@ function resolveDshHome(env = process.env) {
186
193
  /**
187
194
  * Install the packaged `preset/data-agent/` directory into
188
195
  * `$DSH_HOME/.agent-presets/<presetId>/`. Idempotent: an existing target is
189
- * normally left untouched. The exact package-owned 0.0.9 composition is
190
- * migrated once because its two dynamic package rows are incompatible with
191
- * DSH Desktop's unpacked-ASAR loader; user-edited compositions are never
192
- * overwritten. `installPreset: false` never calls this. Best-effort a
193
- * failure logs a warning with manual install instructions instead of failing
194
- * the boot.
196
+ * normally left untouched. Exact package-owned legacy compositions are
197
+ * migrated once when their runtime contract changes; user-edited compositions
198
+ * are never overwritten. `installPreset: false` never calls this. Best-effort
199
+ * a failure logs a warning with manual install instructions instead of
200
+ * failing the boot.
195
201
  */
196
202
  async function installPreset(ctx, presetId) {
197
203
  const targetDir = join(resolveDshHome(), ".agent-presets", presetId);
@@ -210,13 +216,13 @@ async function installPreset(ctx, presetId) {
210
216
  return false;
211
217
  }
212
218
  }
213
- /** SHA-256 of the unmodified 0.0.9 composition that imported /tool and /command dynamically. */
214
- const LEGACY_PRESET_0_0_9_SHA256 = "bae875a90d638ea78715030246b0f8a9f1a2c3359ca61febb6ceb59d0fcd930a";
219
+ /** SHA-256 values of unmodified package-owned compositions safe to migrate. */
220
+ const LEGACY_MANAGED_PRESET_SHA256 = /* @__PURE__ */ new Set(["bae875a90d638ea78715030246b0f8a9f1a2c3359ca61febb6ceb59d0fcd930a", "d3c6f4049580069eec1c6b7de101f12c7fb30482ad317434afb69afb08a91fc6"]);
215
221
  /** Public for regression tests of the non-destructive preset migration gate. */
216
222
  function isLegacyManagedPreset(source) {
217
- return createHash("sha256").update(source).digest("hex") === LEGACY_PRESET_0_0_9_SHA256;
223
+ return LEGACY_MANAGED_PRESET_SHA256.has(createHash("sha256").update(source).digest("hex"));
218
224
  }
219
- /** Upgrade only the exact package-owned legacy composition; preserve every edited preset. */
225
+ /** Upgrade only exact package-owned legacy compositions; preserve every edited preset. */
220
226
  async function synchronizeExistingPreset(ctx, targetDir, sourceDir, presetId) {
221
227
  const composition = join(targetDir, "agent.cordis.yml");
222
228
  try {
@@ -224,7 +230,7 @@ async function synchronizeExistingPreset(ctx, targetDir, sourceDir, presetId) {
224
230
  if (isLegacyManagedPreset(current)) {
225
231
  const replacement = await readFile(join(sourceDir, "agent.cordis.yml"), "utf8");
226
232
  await writeFile(composition, replacement, "utf8");
227
- ctx.logger.info("data-agent: migrated preset at %s to profile-preloaded tools (removed dynamic /tool and /command rows)", composition);
233
+ ctx.logger.info("data-agent: migrated package-owned preset at %s to the current runtime contract", composition);
228
234
  return true;
229
235
  }
230
236
  if (current.includes("@yejiming/dsh-data-agent/tool") || current.includes("@yejiming/dsh-data-agent/command")) {
@@ -307,7 +313,8 @@ async function apply(ctx, config) {
307
313
  ...spec.port !== void 0 ? { port: spec.port } : {},
308
314
  ...spec.user !== void 0 ? { user: spec.user } : {},
309
315
  ...spec.passwordRef !== void 0 ? { passwordRef: spec.passwordRef } : {},
310
- ...spec.readonly !== void 0 ? { readonly: spec.readonly } : {}
316
+ ...spec.readonly !== void 0 ? { readonly: spec.readonly } : {},
317
+ ...spec.secure !== void 0 ? { secure: spec.secure } : {}
311
318
  };
312
319
  store.set(sessionId, connection);
313
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 };