@lark-apaas/miaoda-cli 0.1.0-alpha.097a394

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 (48) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +66 -0
  3. package/bin/miaoda.js +2 -0
  4. package/dist/api/db/api.js +160 -0
  5. package/dist/api/db/client.js +108 -0
  6. package/dist/api/db/index.js +16 -0
  7. package/dist/api/db/parsers.js +157 -0
  8. package/dist/api/db/types.js +10 -0
  9. package/dist/api/file/api.js +420 -0
  10. package/dist/api/file/client.js +161 -0
  11. package/dist/api/file/detect.js +56 -0
  12. package/dist/api/file/index.js +17 -0
  13. package/dist/api/file/parsers.js +72 -0
  14. package/dist/api/file/types.js +3 -0
  15. package/dist/api/index.js +42 -0
  16. package/dist/api/plugin/api.js +243 -0
  17. package/dist/api/plugin/index.js +12 -0
  18. package/dist/api/plugin/types.js +3 -0
  19. package/dist/cli/commands/db/index.js +73 -0
  20. package/dist/cli/commands/file/index.js +67 -0
  21. package/dist/cli/commands/index.js +11 -0
  22. package/dist/cli/commands/plugin/index.js +204 -0
  23. package/dist/cli/commands/shared.js +53 -0
  24. package/dist/cli/handlers/db/data.js +167 -0
  25. package/dist/cli/handlers/db/index.js +11 -0
  26. package/dist/cli/handlers/db/schema.js +161 -0
  27. package/dist/cli/handlers/db/sql.js +173 -0
  28. package/dist/cli/handlers/file/cp.js +220 -0
  29. package/dist/cli/handlers/file/helpers.js +16 -0
  30. package/dist/cli/handlers/file/index.js +13 -0
  31. package/dist/cli/handlers/file/ls.js +109 -0
  32. package/dist/cli/handlers/file/rm.js +243 -0
  33. package/dist/cli/handlers/file/sign.js +96 -0
  34. package/dist/cli/handlers/file/stat.js +97 -0
  35. package/dist/cli/handlers/index.js +19 -0
  36. package/dist/cli/handlers/plugin/index.js +10 -0
  37. package/dist/cli/handlers/plugin/plugin-local.js +382 -0
  38. package/dist/cli/handlers/plugin/plugin.js +308 -0
  39. package/dist/cli/handlers/shared.js +139 -0
  40. package/dist/main.js +31 -0
  41. package/dist/utils/config.js +31 -0
  42. package/dist/utils/error.js +35 -0
  43. package/dist/utils/http.js +46 -0
  44. package/dist/utils/index.js +25 -0
  45. package/dist/utils/log_id.js +13 -0
  46. package/dist/utils/logger.js +15 -0
  47. package/dist/utils/output.js +59 -0
  48. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Lark Technologies Pte. Ltd. and/or its affiliates
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
8
+ IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
9
+ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
10
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
11
+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
12
+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # miaoda-cli
2
+
3
+ 妙搭开发平台 Code Agent 命令行工具,为 Agent 提供原子能力。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add -g @lark-apaas/miaoda-cli
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ```bash
14
+ # 设置默认应用(需要应用上下文的命令可用 --app-id 覆盖)
15
+ export MIAODA_APP_ID=app_demo_xxx
16
+
17
+ # 插件管理
18
+ miaoda plugin list-packages
19
+ miaoda plugin install @demo/example-plugin
20
+ ```
21
+
22
+ JSON 结构化输出(Agent 推荐):
23
+
24
+ ```bash
25
+ # 输出全部字段
26
+ miaoda plugin list --json
27
+
28
+ # 可选字段级选择
29
+ miaoda plugin list --json id,name
30
+
31
+ # 或通过 --output 指定格式
32
+ miaoda plugin list --output json
33
+ ```
34
+
35
+ ## 命令树
36
+
37
+ 命令以"域"作为第一级命名空间:
38
+
39
+ | 域 | 用途 |
40
+ |---|---|
41
+ | `miaoda plugin ...` | 插件管理:安装、更新、移除、插件实例查询 |
42
+
43
+ 完整命令通过 `miaoda --help` 或 `miaoda <domain> --help` 查看。
44
+
45
+ ## 全局参数
46
+
47
+ - `--json [fields]`:输出结构化 JSON,可选字段级选择(如 `--json id,name`)。
48
+ - `--output <pretty|json>`:输出格式,默认 `pretty`。
49
+ - `--verbose`:输出调试日志到 stderr。
50
+
51
+ > `--app-id <id>` 非全局参数,需要应用上下文的命令会各自声明,默认读取 `MIAODA_APP_ID` 环境变量。
52
+
53
+ ## 环境变量
54
+
55
+ - `MIAODA_APP_ID`:默认应用 ID,等价于 `--app-id`。
56
+ - `MIAODA_CANARY_HEADER`:设置后所有 HTTP 请求自动带上 `x-tt-env: <值>` 小流量头(例:`export MIAODA_CANARY_HEADER=boe_xxx`)。
57
+
58
+ ## 输出契约
59
+
60
+ - 默认文本输出(pretty),`--json` 或 `--output json` 切换为 JSON。
61
+ - 错误始终写 stderr,业务数据写 stdout。
62
+ - 完整的输出格式、JSON envelope、错误 shape、错误码前缀、退出码分类等见 [Agent 友好 CLI 设计规范](./docs/agent-friendly-cli.md)。
63
+
64
+ ## 贡献
65
+
66
+ 详见 [AGENTS.md](./AGENTS.md)。
package/bin/miaoda.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("../dist/main.js");
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.execSql = execSql;
4
+ exports.getSchema = getSchema;
5
+ exports.importData = importData;
6
+ exports.exportData = exportData;
7
+ const http_1 = require("../../utils/http");
8
+ const error_1 = require("../../utils/error");
9
+ const client_1 = require("./client");
10
+ /** dataloom 默认 dbBranch —— 单环境应用均传 main */
11
+ const DEFAULT_DB_BRANCH = "main";
12
+ // ── db sql → InnerExecuteSQL ──
13
+ /**
14
+ * 执行 SQL。
15
+ * 后端:POST /v1/app/{appId}/dataloom/sql?dbBranch=main
16
+ *
17
+ * 返回所有 results[](多条语句时每条一项);CLI 侧按 PRD 仅取最后一条展示,
18
+ * 但 API 层保留完整列表以便测试和高级用法。
19
+ */
20
+ async function execSql(opts) {
21
+ const client = (0, http_1.getHttpClient)();
22
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/sql", {
23
+ dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
24
+ });
25
+ const response = await client.post(url, { sql: opts.sql });
26
+ if (!response.ok) {
27
+ // 4xx / 5xx:尝试解析 body 取 status_code 映射业务错误
28
+ let body = null;
29
+ try {
30
+ body = (await response.json());
31
+ }
32
+ catch {
33
+ // ignore
34
+ }
35
+ if (body)
36
+ (0, client_1.extractData)(body);
37
+ throw new error_1.HttpError(response.status, url, `Failed to execute SQL: ${String(response.status)} ${response.statusText}`);
38
+ }
39
+ const body = (await response.json());
40
+ const data = (0, client_1.extractData)(body);
41
+ return data.results ?? [];
42
+ }
43
+ // ── db schema → InnerGetSchema ──
44
+ /**
45
+ * 查询 schema。
46
+ * 后端:GET /v1/app/{appId}/dataloom/schema?format=schema|ddl&tableNames=...&includeStats=...&dbBranch=main
47
+ *
48
+ * 返回 body.data(含 `schema` 或 `ddl`)供 handler 使用。
49
+ */
50
+ async function getSchema(opts) {
51
+ const client = (0, http_1.getHttpClient)();
52
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/schema", {
53
+ format: opts.format ?? "schema",
54
+ tableNames: opts.tableNames,
55
+ includeStats: opts.includeStats ? "true" : undefined,
56
+ dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
57
+ });
58
+ const response = await client.get(url);
59
+ if (!response.ok) {
60
+ let body = null;
61
+ try {
62
+ body = (await response.json());
63
+ }
64
+ catch {
65
+ // ignore
66
+ }
67
+ if (body)
68
+ (0, client_1.extractData)(body);
69
+ throw new error_1.HttpError(response.status, url, `Failed to get schema: ${String(response.status)} ${response.statusText}`);
70
+ }
71
+ const body = (await response.json());
72
+ return (0, client_1.extractData)(body);
73
+ }
74
+ // ── db data import → InnerImportData ──
75
+ /**
76
+ * 导入文件。
77
+ * 后端:POST /v1/app/{appId}/dataloom/data/import?tableName=...&format=csv|json&dbBranch=main
78
+ *
79
+ * Body 为原始文件字节(不走 JSON envelope)。
80
+ */
81
+ async function importData(opts) {
82
+ const client = (0, http_1.getHttpClient)();
83
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/data/import", {
84
+ tableName: opts.tableName,
85
+ format: opts.format,
86
+ dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
87
+ });
88
+ const contentType = opts.format === "csv" ? "text/csv" : "application/json";
89
+ const ab = opts.body.buffer.slice(opts.body.byteOffset, opts.body.byteOffset + opts.body.byteLength);
90
+ const response = await client.request({
91
+ method: "POST",
92
+ url,
93
+ headers: { "Content-Type": contentType },
94
+ body: ab,
95
+ });
96
+ if (!response.ok) {
97
+ let body = null;
98
+ try {
99
+ body = (await response.json());
100
+ }
101
+ catch {
102
+ // ignore
103
+ }
104
+ if (body)
105
+ (0, client_1.extractData)(body);
106
+ throw new error_1.HttpError(response.status, url, `Failed to import data: ${String(response.status)} ${response.statusText}`);
107
+ }
108
+ // 后端 InnerImportData 响应里 data 直接返 {tableName, rows, durationMs}
109
+ const body = (await response.json());
110
+ const data = (0, client_1.extractData)(body);
111
+ return {
112
+ tableName: data.tableName ?? opts.tableName,
113
+ rows: data.rows ?? 0,
114
+ durationMs: data.durationMs ?? 0,
115
+ };
116
+ }
117
+ // ── db data export → InnerExportData ──
118
+ /**
119
+ * 导出数据。
120
+ * 后端:POST /v1/app/{appId}/dataloom/data/export?tableName=...&format=csv|json&dbBranch=main
121
+ *
122
+ * 响应 body 为原始 CSV/JSON 字节(不走 envelope)。错误仍通过 HTTP 4xx/5xx + BaseResp 传达。
123
+ */
124
+ async function exportData(opts) {
125
+ const client = (0, http_1.getHttpClient)();
126
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/data/export", {
127
+ tableName: opts.tableName,
128
+ format: opts.format,
129
+ dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
130
+ });
131
+ const reqBody = { limit: opts.limit ?? 5000 };
132
+ const response = await client.post(url, reqBody);
133
+ if (!response.ok) {
134
+ // 错误路径:body 是 JSON envelope
135
+ let body = null;
136
+ try {
137
+ body = (await response.json());
138
+ }
139
+ catch {
140
+ // ignore
141
+ }
142
+ if (body)
143
+ (0, client_1.extractData)(body);
144
+ throw new error_1.HttpError(response.status, url, `Failed to export data: ${String(response.status)} ${response.statusText}`);
145
+ }
146
+ // 成功路径:响应 body 是原始 CSV/JSON 字节
147
+ const contentType = response.headers.get("Content-Type") ??
148
+ (opts.format === "csv" ? "text/csv" : "application/json");
149
+ const ab = await response.arrayBuffer();
150
+ const buf = Buffer.from(new Uint8Array(ab));
151
+ if (buf.length === 0) {
152
+ throw new error_1.AppError("INTERNAL_DB_ERROR", "Empty export response body");
153
+ }
154
+ return {
155
+ tableName: opts.tableName,
156
+ format: opts.format,
157
+ contentType,
158
+ body: buf,
159
+ };
160
+ }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SQLSTATE_MAP = void 0;
4
+ exports.ensureInnerSuccess = ensureInnerSuccess;
5
+ exports.extractData = extractData;
6
+ exports.buildInnerUrl = buildInnerUrl;
7
+ const error_1 = require("../../utils/error");
8
+ /**
9
+ * 校验 dataloom InnerAPI 响应的 envelope。
10
+ *
11
+ * 真实响应结构:`{ data: {...业务字段...}, status_code: "0" }`
12
+ * - `status_code == "0"` 或缺省视为成功
13
+ * - 其他值视为业务错误,按业务 code 或 SQLSTATE 映射到 CLI code
14
+ */
15
+ function ensureInnerSuccess(body) {
16
+ if (!body) {
17
+ throw new error_1.AppError("INTERNAL_DB_ERROR", "empty response body");
18
+ }
19
+ const code = body.status_code ?? body.ErrorCode ?? "0";
20
+ if (code === "0" || code === "")
21
+ return;
22
+ const message = body.error_msg ?? body.ErrorMessage ?? `dataloom API error [${code}]`;
23
+ // k_dl_1300002 是 PG 执行透传错误;error_msg 里常带 SQLSTATE,优先按 SQLSTATE 映射
24
+ if (code === "k_dl_1300002") {
25
+ const sqlstate = extractSqlstate(message);
26
+ if (sqlstate && exports.SQLSTATE_MAP[sqlstate]) {
27
+ throw new error_1.AppError(exports.SQLSTATE_MAP[sqlstate], message);
28
+ }
29
+ }
30
+ // k_dl_1600000 是 dataloom 通用参数错误,不能整体映射;
31
+ // 只有 message 以 "Invalid DB Branch" 开头的情况对应"用户传了 --env 但 dbBranch 不存在 / 多环境未初始化",
32
+ // 按技术方案固定映射为 MULTI_ENV_NOT_INITIALIZED(含固定 message 与 hint)。
33
+ if (code === "k_dl_1600000" && message.startsWith("Invalid DB Branch")) {
34
+ throw new error_1.AppError("MULTI_ENV_NOT_INITIALIZED", "--env is not available (multi-env not initialized)", {
35
+ next_actions: [
36
+ "Current app uses a single database for dev and production. Run `miaoda db migration init` to set up multi-env.",
37
+ ],
38
+ });
39
+ }
40
+ // 业务 code 优先映射到语义 CLI code;未知 code 透传为 DB_API_<code> 样式
41
+ const mapped = BIZ_ERR_MAP.get(code);
42
+ if (mapped) {
43
+ throw new error_1.AppError(mapped.code, mapped.message ?? message, {
44
+ next_actions: mapped.hint ? [mapped.hint] : undefined,
45
+ });
46
+ }
47
+ // 兜底:dataloom 未映射的 code 原样透传
48
+ throw new error_1.AppError(`DB_API_${code}`, message);
49
+ }
50
+ /** 从 PG 执行错误消息里提取 "(SQLSTATE XXXXX)"。 */
51
+ function extractSqlstate(msg) {
52
+ const m = /SQLSTATE\s+([0-9A-Z]{5})/.exec(msg);
53
+ return m?.[1];
54
+ }
55
+ /**
56
+ * dataloom 业务错误码 → CLI 语义化 code 映射。
57
+ *
58
+ * 长期维护,观察到新 code 在此增补。
59
+ * 常见 dataloom 错误:详见 `common/dataloom_error/` 错误码定义(范围 `k_dl_*xxxxx`)。
60
+ */
61
+ const BIZ_ERR_MAP = new Map(Object.entries({
62
+ // 来源:真实环境探测(dataloom InnerExecuteSQL / InnerGetSchema)
63
+ // k_dl_000001:DB 连接 / QPS 限流 等基础设施层错误
64
+ "k_dl_000001": { code: "INTERNAL_DB_ERROR", hint: "检查 dbBranch 与应用 PG 实例状态,或稍后重试" },
65
+ // k_dl_000002:SQL 解析 / 不支持的 SQL 类型(VACUUM、COPY、SET、SHOW 等)
66
+ "k_dl_000002": { code: "SQL_SYNTAX_ERROR" },
67
+ // k_dl_000003:查询命中系统表(pg_tables / pg_user 等)被拒
68
+ "k_dl_000003": { code: "SQL_OPERATION_FORBIDDEN" },
69
+ // k_dl_1300002:PG 执行错误;实际 SQLSTATE 由 extractSqlstate 单独映射
70
+ // 未匹配到 SQLSTATE 时走下面兜底 DB_API_<code>
71
+ }));
72
+ /** PG SQLSTATE → CLI code(当前 dataloom 不一定透传,预留未来使用) */
73
+ exports.SQLSTATE_MAP = {
74
+ "42601": "SQL_SYNTAX_ERROR",
75
+ "42P01": "TABLE_NOT_FOUND",
76
+ "42703": "COLUMN_NOT_FOUND",
77
+ "57014": "STATEMENT_TIMEOUT",
78
+ "23505": "UNIQUE_VIOLATION",
79
+ "22P02": "TYPE_MISMATCH",
80
+ };
81
+ /**
82
+ * 从 envelope 里取 data 业务字段,校验成功后返回;失败则 throw。
83
+ */
84
+ function extractData(body) {
85
+ ensureInnerSuccess(body);
86
+ if (!body?.data) {
87
+ throw new error_1.AppError("INTERNAL_DB_ERROR", "response missing data field");
88
+ }
89
+ return body.data;
90
+ }
91
+ /**
92
+ * 组装 `/v1/app/{appId}/dataloom/...` URL。
93
+ */
94
+ function buildInnerUrl(appId, path, query) {
95
+ const normalized = path.startsWith("/") ? path : `/${path}`;
96
+ let url = `/v1/app/${encodeURIComponent(appId)}/dataloom${normalized}`;
97
+ if (query) {
98
+ const usp = new URLSearchParams();
99
+ for (const [k, v] of Object.entries(query)) {
100
+ if (v !== undefined && v !== "")
101
+ usp.append(k, v);
102
+ }
103
+ const qs = usp.toString();
104
+ if (qs)
105
+ url += `?${qs}`;
106
+ }
107
+ return url;
108
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pickTableDetail = exports.flattenSchemaList = exports.parseSqlResult = exports.SQLSTATE_MAP = exports.ensureInnerSuccess = exports.buildInnerUrl = exports.exportData = exports.importData = exports.getSchema = exports.execSql = void 0;
4
+ var api_1 = require("./api");
5
+ Object.defineProperty(exports, "execSql", { enumerable: true, get: function () { return api_1.execSql; } });
6
+ Object.defineProperty(exports, "getSchema", { enumerable: true, get: function () { return api_1.getSchema; } });
7
+ Object.defineProperty(exports, "importData", { enumerable: true, get: function () { return api_1.importData; } });
8
+ Object.defineProperty(exports, "exportData", { enumerable: true, get: function () { return api_1.exportData; } });
9
+ var client_1 = require("./client");
10
+ Object.defineProperty(exports, "buildInnerUrl", { enumerable: true, get: function () { return client_1.buildInnerUrl; } });
11
+ Object.defineProperty(exports, "ensureInnerSuccess", { enumerable: true, get: function () { return client_1.ensureInnerSuccess; } });
12
+ Object.defineProperty(exports, "SQLSTATE_MAP", { enumerable: true, get: function () { return client_1.SQLSTATE_MAP; } });
13
+ var parsers_1 = require("./parsers");
14
+ Object.defineProperty(exports, "parseSqlResult", { enumerable: true, get: function () { return parsers_1.parseSqlResult; } });
15
+ Object.defineProperty(exports, "flattenSchemaList", { enumerable: true, get: function () { return parsers_1.flattenSchemaList; } });
16
+ Object.defineProperty(exports, "pickTableDetail", { enumerable: true, get: function () { return parsers_1.pickTableDetail; } });
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSqlResult = parseSqlResult;
4
+ exports.flattenSchemaList = flattenSchemaList;
5
+ exports.pickTableDetail = pickTableDetail;
6
+ const error_1 = require("../../utils/error");
7
+ // ── SQL 结果 ──
8
+ /**
9
+ * 解析 InnerExecuteSQL 的单条 results[](PRD 要求多条语句只取最后一条)。
10
+ */
11
+ function parseSqlResult(r) {
12
+ if (r.sqlType === "SELECT") {
13
+ let rows = [];
14
+ if (r.data) {
15
+ try {
16
+ const parsed = JSON.parse(r.data);
17
+ if (Array.isArray(parsed))
18
+ rows = parsed;
19
+ }
20
+ catch {
21
+ throw new error_1.AppError("INTERNAL_DB_ERROR", "Failed to parse SELECT result JSON");
22
+ }
23
+ }
24
+ return {
25
+ kind: "select",
26
+ rows,
27
+ recordCount: r.recordCount ?? rows.length,
28
+ };
29
+ }
30
+ if (r.sqlType === "INSERT" || r.sqlType === "UPDATE" || r.sqlType === "DELETE") {
31
+ const affected = r.affectedRows ?? extractRowCount(r.data);
32
+ return {
33
+ kind: "dml",
34
+ sqlType: r.sqlType,
35
+ affectedRows: affected,
36
+ };
37
+ }
38
+ // DDL or unknown
39
+ return { kind: "ddl" };
40
+ }
41
+ /** DML 的 data 通常是 `[{"rowCount": N}]`;兜底从这里读影响行数。 */
42
+ function extractRowCount(data) {
43
+ if (!data)
44
+ return 0;
45
+ try {
46
+ const parsed = JSON.parse(data);
47
+ if (Array.isArray(parsed) && parsed.length > 0) {
48
+ const first = parsed[0];
49
+ const v = first.rowCount ?? first.affected_rows;
50
+ if (typeof v === "number")
51
+ return v;
52
+ }
53
+ }
54
+ catch {
55
+ // ignore
56
+ }
57
+ return 0;
58
+ }
59
+ // ── schema ──
60
+ /**
61
+ * 合并 tables / views / materializedViews 为扁平 TableSummary[](schema list 视图)。
62
+ * 统计信息(estimated_row_count / size_bytes)来自 `root.tableStats[tableName]`,
63
+ * 需请求带 `includeStats=true` 才会有。
64
+ */
65
+ function flattenSchemaList(root) {
66
+ if (!root)
67
+ return [];
68
+ const statsMap = root.tableStats ?? {};
69
+ const out = [];
70
+ for (const t of root.tables?.data ?? [])
71
+ out.push(toSummary(t, statsMap[t.tableName]));
72
+ for (const v of root.views?.data ?? [])
73
+ out.push(toSummary(v, statsMap[v.tableName]));
74
+ for (const mv of root.materializedViews?.data ?? [])
75
+ out.push(toSummary(mv, statsMap[mv.tableName]));
76
+ return out;
77
+ }
78
+ function toSummary(t, stats) {
79
+ return {
80
+ name: t.tableName,
81
+ description: t.comment && t.comment.length > 0 ? t.comment : null,
82
+ columns: (t.fields ?? []).length,
83
+ estimated_row_count: typeof stats?.estimatedRowCount === "number" ? stats.estimatedRowCount : null,
84
+ size_bytes: typeof stats?.sizeBytes === "number" ? stats.sizeBytes : null,
85
+ updated_at: t.updatedAt,
86
+ };
87
+ }
88
+ /**
89
+ * 从 InnerSchemaRespVO 里定位单表 → 转 CLI TableDetail。
90
+ * 依次查 tables / views / materializedViews;找不到返回 null。
91
+ */
92
+ function pickTableDetail(root, tableName) {
93
+ if (!root)
94
+ return null;
95
+ const stats = root.tableStats?.[tableName];
96
+ const pools = [
97
+ root.tables?.data,
98
+ root.views?.data,
99
+ root.materializedViews?.data,
100
+ ];
101
+ for (const arr of pools) {
102
+ if (!arr)
103
+ continue;
104
+ const hit = arr.find((t) => t.tableName === tableName);
105
+ if (hit)
106
+ return toDetail(hit, stats);
107
+ }
108
+ return null;
109
+ }
110
+ function toDetail(t, stats) {
111
+ // 优先用 tableStats.indexes(PRIMARY + UNIQUE + INDEX 统一视图);
112
+ // 回退到 TableVO.indexes(仅二级索引,老后端没返回 tableStats 时使用)
113
+ const rawIndexes = stats?.indexes ?? t.indexes ?? [];
114
+ return {
115
+ name: t.tableName,
116
+ description: t.comment && t.comment.length > 0 ? t.comment : null,
117
+ columns: (t.fields ?? []).map(toColumn),
118
+ indexes: rawIndexes.map(toIndex),
119
+ estimated_row_count: typeof stats?.estimatedRowCount === "number" ? stats.estimatedRowCount : null,
120
+ size_bytes: typeof stats?.sizeBytes === "number" ? stats.sizeBytes : null,
121
+ created_at: t.createdAt,
122
+ updated_at: t.updatedAt,
123
+ };
124
+ }
125
+ function toColumn(f) {
126
+ const type = f.arrayElementType ? `${f.arrayElementType}[]` : f.type;
127
+ return {
128
+ name: f.fieldName,
129
+ type,
130
+ nullable: f.isNullable ?? false,
131
+ default: f.defaultValue ?? null,
132
+ // 后端 comment *string 在没有 comment 时可能返 "" 或 null,这里统一归一为 null(对齐 PRD)
133
+ comment: f.comment && f.comment.length > 0 ? f.comment : null,
134
+ };
135
+ }
136
+ function toIndex(i) {
137
+ return {
138
+ name: i.indexName,
139
+ type: translateIndexType(i.indexType),
140
+ columns: i.indexColumns ?? [],
141
+ };
142
+ }
143
+ /**
144
+ * 把后端原始 IndexType(小写:primary / unique / normal / foreign)翻译为 PRD 约定的展示值。
145
+ * 规则:
146
+ * primary → "PRIMARY KEY"
147
+ * unique → "UNIQUE"
148
+ * 其它 → "INDEX"(foreign 外键通过 relationships 表达,不进 indexes;真的遇到也归 INDEX)
149
+ */
150
+ function translateIndexType(raw) {
151
+ const s = raw.toLowerCase();
152
+ if (s === "primary")
153
+ return "PRIMARY KEY";
154
+ if (s === "unique")
155
+ return "UNIQUE";
156
+ return "INDEX";
157
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ // ── dataloom InnerAPI 响应信封 ──
3
+ //
4
+ // 实际响应结构(与 file-storage 类似):
5
+ // { "data": { ...业务字段... }, "status_code": "0", "error_msg": "..." }
6
+ //
7
+ // - `status_code == "0"` 成功
8
+ // - `status_code != "0"` 业务错误(如 PG SQLSTATE 映射后的 code)
9
+ // - 业务字段统一在 `data` 下:`results` / `schema` / `ddl` 等
10
+ Object.defineProperty(exports, "__esModule", { value: true });