@kweaver-ai/kweaver-sdk 0.4.10 → 0.4.12
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.
- package/README.md +61 -3
- package/README.zh.md +42 -1
- package/dist/api/dataflow.d.ts +78 -0
- package/dist/api/dataflow.js +135 -0
- package/dist/api/dataviews.d.ts +58 -1
- package/dist/api/dataviews.js +150 -1
- package/dist/auth/oauth.d.ts +6 -1
- package/dist/auth/oauth.js +240 -166
- package/dist/cli.js +13 -1
- package/dist/client.d.ts +12 -0
- package/dist/client.js +18 -0
- package/dist/commands/auth.js +36 -16
- package/dist/commands/bkn.js +214 -21
- package/dist/commands/dataview.d.ts +1 -0
- package/dist/commands/dataview.js +244 -0
- package/dist/commands/ds.d.ts +16 -0
- package/dist/commands/ds.js +204 -1
- package/dist/commands/import-csv.d.ts +47 -0
- package/dist/commands/import-csv.js +111 -0
- package/dist/config/store.d.ts +2 -0
- package/dist/config/tls-env.d.ts +8 -0
- package/dist/config/tls-env.js +22 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/resources/dataflows.d.ts +17 -0
- package/dist/resources/dataflows.js +22 -0
- package/dist/resources/datasources.d.ts +52 -0
- package/dist/resources/datasources.js +54 -0
- package/dist/resources/dataviews.d.ts +28 -0
- package/dist/resources/dataviews.js +34 -0
- package/dist/resources/vega.d.ts +41 -0
- package/dist/resources/vega.js +80 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -90,6 +90,29 @@ const graph = await client.bkn.querySubgraph("bkn-id", { /* path spec */ });
|
|
|
90
90
|
await client.bkn.executeAction("bkn-id", "at-id", { /* params */ });
|
|
91
91
|
const logs = await client.bkn.listActionLogs("bkn-id");
|
|
92
92
|
|
|
93
|
+
// Data sources & data views
|
|
94
|
+
const dsList = await client.datasources.list();
|
|
95
|
+
const tables = await client.datasources.listTables("ds-id");
|
|
96
|
+
const viewId = await client.dataviews.create({ name: "v", datasourceId: "ds-id", table: "orders" });
|
|
97
|
+
const views = await client.dataviews.list({ datasourceId: "ds-id" });
|
|
98
|
+
const fuzzy = await client.dataviews.find("BOM", { wait: false });
|
|
99
|
+
const exact = await client.dataviews.find("orders", {
|
|
100
|
+
datasourceId: "ds-id",
|
|
101
|
+
exact: true,
|
|
102
|
+
wait: true,
|
|
103
|
+
});
|
|
104
|
+
const dv = await client.dataviews.get(viewId);
|
|
105
|
+
|
|
106
|
+
// Dataflow automation (CSV import pipeline, etc.)
|
|
107
|
+
const result = await client.dataflows.execute({
|
|
108
|
+
title: "import", trigger_config: { operator: "manual" },
|
|
109
|
+
steps: [{ id: "s1", title: "load", operator: "csv_import", parameters: {} }],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Vega observability
|
|
113
|
+
const catalogs = await client.vega.listCatalogs();
|
|
114
|
+
const health = await client.vega.health();
|
|
115
|
+
|
|
93
116
|
// Context Loader (semantic search over a BKN via MCP)
|
|
94
117
|
const cl = client.contextLoader(mcpUrl, "bkn-id");
|
|
95
118
|
const results = await cl.search({ query: "hypertension treatment" });
|
|
@@ -98,16 +121,24 @@ const results = await cl.search({ query: "hypertension treatment" });
|
|
|
98
121
|
## CLI Reference
|
|
99
122
|
|
|
100
123
|
```
|
|
101
|
-
kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] — also: status, list, use, delete, logout
|
|
124
|
+
kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k] — also: status, list, use, delete, logout
|
|
102
125
|
kweaver token
|
|
126
|
+
kweaver config show / set-bd <value>
|
|
127
|
+
kweaver ds list/get/delete/tables/connect
|
|
128
|
+
kweaver ds import-csv <ds_id> --files <glob> [--table-prefix <p>] [--batch-size 500]
|
|
129
|
+
kweaver dataview list/find/get/delete
|
|
103
130
|
kweaver bkn list/get/stats/export/create/update/delete
|
|
131
|
+
kweaver bkn create-from-ds <ds_id> --name <name> [--tables t1,t2] [--build]
|
|
132
|
+
kweaver bkn create-from-csv <ds_id> --files <glob> --name <name> [--build]
|
|
133
|
+
kweaver bkn validate/push/pull
|
|
104
134
|
kweaver bkn object-type list/get/create/update/delete/query/properties
|
|
105
135
|
kweaver bkn relation-type list/get/create/update/delete
|
|
106
136
|
kweaver bkn action-type list/query/execute
|
|
107
|
-
kweaver bkn subgraph
|
|
137
|
+
kweaver bkn subgraph / search
|
|
108
138
|
kweaver bkn action-execution get
|
|
109
139
|
kweaver bkn action-log list/get/cancel
|
|
110
|
-
kweaver agent list/get/chat/sessions/history
|
|
140
|
+
kweaver agent list/get/create/update/delete/chat/sessions/history/publish/unpublish
|
|
141
|
+
kweaver vega health/stats/inspect/catalog/resource/connector-type
|
|
111
142
|
kweaver context-loader config set/use/list/show
|
|
112
143
|
kweaver context-loader kn-search/query-object-instance/...
|
|
113
144
|
kweaver call <path> [-X METHOD] [-d BODY] [-H header]
|
|
@@ -120,6 +151,33 @@ kweaver call <path> [-X METHOD] [-d BODY] [-H header]
|
|
|
120
151
|
| `KWEAVER_BASE_URL` | KWeaver instance URL |
|
|
121
152
|
| `KWEAVER_BUSINESS_DOMAIN` | Business domain identifier |
|
|
122
153
|
| `KWEAVER_TOKEN` | Access token |
|
|
154
|
+
| `KWEAVER_TLS_INSECURE` | Set to `1` or `true` to skip TLS certificate verification for all HTTPS in the process (dev only; prefer `kweaver auth … --insecure` which saves per platform) |
|
|
155
|
+
| `NODE_TLS_REJECT_UNAUTHORIZED` | Node.js built-in TLS switch: set to `0` to skip certificate verification for HTTPS in this process. The `kweaver` CLI sets this when `KWEAVER_TLS_INSECURE` is set or the saved token has insecure TLS (same scope as above; dev only). |
|
|
156
|
+
|
|
157
|
+
### TLS Certificate Troubleshooting
|
|
158
|
+
|
|
159
|
+
If you encounter errors like `fetch failed`, `self-signed certificate`, or `UNABLE_TO_GET_ISSUER_CERT`, the target server likely uses a self-signed certificate or Kubernetes Ingress default fake certificate. Try the following in order of preference:
|
|
160
|
+
|
|
161
|
+
1. **Recommended (persists per platform)** — add `--insecure` during login:
|
|
162
|
+
```bash
|
|
163
|
+
kweaver auth login https://your-host --insecure
|
|
164
|
+
# or shorthand
|
|
165
|
+
kweaver auth login https://your-host -k
|
|
166
|
+
```
|
|
167
|
+
The flag is saved to `token.json` in `~/.kweaver/`, so all subsequent CLI commands for that platform skip TLS verification automatically.
|
|
168
|
+
|
|
169
|
+
2. **Temporary (current shell)** — set an environment variable:
|
|
170
|
+
```bash
|
|
171
|
+
export KWEAVER_TLS_INSECURE=1
|
|
172
|
+
kweaver bkn list
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
3. **Node.js native** — set `NODE_TLS_REJECT_UNAUTHORIZED` directly:
|
|
176
|
+
```bash
|
|
177
|
+
NODE_TLS_REJECT_UNAUTHORIZED=0 kweaver bkn list
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
> **Security note:** All of the above disable HTTPS certificate verification and should only be used in development or internal network environments. Use trusted CA-signed certificates in production.
|
|
123
181
|
|
|
124
182
|
## Using with AI Agents
|
|
125
183
|
|
package/README.zh.md
CHANGED
|
@@ -90,6 +90,18 @@ const graph = await client.bkn.querySubgraph("bkn-id", { /* 路径规格 */
|
|
|
90
90
|
await client.bkn.executeAction("bkn-id", "at-id", { /* 参数 */ });
|
|
91
91
|
const logs = await client.bkn.listActionLogs("bkn-id");
|
|
92
92
|
|
|
93
|
+
// 数据源与数据视图
|
|
94
|
+
const dsList = await client.datasources.list();
|
|
95
|
+
const viewId = await client.dataviews.create({ name: "v", datasourceId: "ds-id", table: "orders" });
|
|
96
|
+
const views = await client.dataviews.list({ datasourceId: "ds-id" });
|
|
97
|
+
const fuzzy = await client.dataviews.find("BOM", { wait: false });
|
|
98
|
+
const exact = await client.dataviews.find("orders", {
|
|
99
|
+
datasourceId: "ds-id",
|
|
100
|
+
exact: true,
|
|
101
|
+
wait: true,
|
|
102
|
+
});
|
|
103
|
+
const dv = await client.dataviews.get(viewId);
|
|
104
|
+
|
|
93
105
|
// Context Loader(通过 MCP 对 BKN 做语义搜索)
|
|
94
106
|
const cl = client.contextLoader(mcpUrl, "bkn-id");
|
|
95
107
|
const results = await cl.search({ query: "高血压 治疗" });
|
|
@@ -98,8 +110,10 @@ const results = await cl.search({ query: "高血压 治疗" });
|
|
|
98
110
|
## 命令速查
|
|
99
111
|
|
|
100
112
|
```
|
|
101
|
-
kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] — 另有 status、list、use、delete、logout
|
|
113
|
+
kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k] — 另有 status、list、use、delete、logout
|
|
102
114
|
kweaver token
|
|
115
|
+
kweaver ds list/get/delete/tables/connect
|
|
116
|
+
kweaver dataview list/find/get/delete
|
|
103
117
|
kweaver bkn list/get/stats/export/create/update/delete
|
|
104
118
|
kweaver bkn object-type list/get/create/update/delete/query/properties
|
|
105
119
|
kweaver bkn relation-type list/get/create/update/delete
|
|
@@ -120,6 +134,33 @@ kweaver call <path> [-X METHOD] [-d BODY] [-H header]
|
|
|
120
134
|
| `KWEAVER_BASE_URL` | KWeaver 实例地址 |
|
|
121
135
|
| `KWEAVER_BUSINESS_DOMAIN` | 业务域标识 |
|
|
122
136
|
| `KWEAVER_TOKEN` | 访问令牌 |
|
|
137
|
+
| `KWEAVER_TLS_INSECURE` | 设为 `1` 或 `true` 时跳过 TLS 证书校验(仅开发;更推荐 `kweaver auth … --insecure` 以按平台持久化) |
|
|
138
|
+
| `NODE_TLS_REJECT_UNAUTHORIZED` | Node.js 内置 TLS 开关:设为 `0` 时在本进程内跳过 HTTPS 证书校验。`kweaver` 在 `KWEAVER_TLS_INSECURE` 生效或已保存 token 为不安全 TLS 时会设置此项(范围同上;仅开发)。 |
|
|
139
|
+
|
|
140
|
+
### TLS 证书问题排查
|
|
141
|
+
|
|
142
|
+
如果遇到 `fetch failed`、`self-signed certificate`、`UNABLE_TO_GET_ISSUER_CERT` 等 TLS 相关错误,通常是目标服务器使用了自签名证书或 Kubernetes Ingress 默认假证书。可按优先级尝试以下方案:
|
|
143
|
+
|
|
144
|
+
1. **推荐(按平台持久化)** — 登录时加 `--insecure`:
|
|
145
|
+
```bash
|
|
146
|
+
kweaver auth login https://your-host --insecure
|
|
147
|
+
# 或简写
|
|
148
|
+
kweaver auth login https://your-host -k
|
|
149
|
+
```
|
|
150
|
+
该标记会写入 `~/.kweaver/` 的 `token.json`,后续所有 CLI 命令对该平台自动生效,无需额外操作。
|
|
151
|
+
|
|
152
|
+
2. **临时生效(当前 shell)** — 设置环境变量:
|
|
153
|
+
```bash
|
|
154
|
+
export KWEAVER_TLS_INSECURE=1
|
|
155
|
+
kweaver bkn list
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
3. **Node.js 原生方式** — 直接设置 `NODE_TLS_REJECT_UNAUTHORIZED`:
|
|
159
|
+
```bash
|
|
160
|
+
NODE_TLS_REJECT_UNAUTHORIZED=0 kweaver bkn list
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> **安全提示:** 以上方式均会跳过 HTTPS 证书校验,仅适用于开发/内网环境。生产环境请使用受信任的 CA 签发证书。
|
|
123
164
|
|
|
124
165
|
## 在 AI 智能体中使用
|
|
125
166
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface DataflowStep {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
operator: string;
|
|
5
|
+
parameters: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface DataflowCreateBody {
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
trigger_config: {
|
|
11
|
+
operator: string;
|
|
12
|
+
};
|
|
13
|
+
steps: DataflowStep[];
|
|
14
|
+
}
|
|
15
|
+
export interface DataflowResult {
|
|
16
|
+
status: "success" | "completed" | "failed" | "error";
|
|
17
|
+
reason?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CreateDataflowOptions {
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
accessToken: string;
|
|
22
|
+
businessDomain?: string;
|
|
23
|
+
body: DataflowCreateBody;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a new dataflow (DAG). Returns the new DAG id.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createDataflow(options: CreateDataflowOptions): Promise<string>;
|
|
29
|
+
export interface RunDataflowOptions {
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
accessToken: string;
|
|
32
|
+
businessDomain?: string;
|
|
33
|
+
dagId: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Trigger a run for an existing dataflow DAG.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runDataflow(options: RunDataflowOptions): Promise<void>;
|
|
39
|
+
export interface PollDataflowOptions {
|
|
40
|
+
baseUrl: string;
|
|
41
|
+
accessToken: string;
|
|
42
|
+
businessDomain?: string;
|
|
43
|
+
dagId: string;
|
|
44
|
+
/** Poll interval in seconds. Default: 3 */
|
|
45
|
+
interval?: number;
|
|
46
|
+
/** Maximum time to wait in seconds. Default: 900 */
|
|
47
|
+
timeout?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Poll GET /api/automation/v1/dag/{dagId}/results until the run is done.
|
|
51
|
+
* Throws on "failed"/"error" status or timeout.
|
|
52
|
+
*/
|
|
53
|
+
export declare function pollDataflowResults(options: PollDataflowOptions): Promise<DataflowResult>;
|
|
54
|
+
export interface DeleteDataflowOptions {
|
|
55
|
+
baseUrl: string;
|
|
56
|
+
accessToken: string;
|
|
57
|
+
businessDomain?: string;
|
|
58
|
+
dagId: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Delete a dataflow DAG. Best-effort — does not throw on errors.
|
|
62
|
+
*/
|
|
63
|
+
export declare function deleteDataflow(options: DeleteDataflowOptions): Promise<void>;
|
|
64
|
+
export interface ExecuteDataflowOptions {
|
|
65
|
+
baseUrl: string;
|
|
66
|
+
accessToken: string;
|
|
67
|
+
businessDomain?: string;
|
|
68
|
+
body: DataflowCreateBody;
|
|
69
|
+
/** Poll interval in seconds. Default: 3 */
|
|
70
|
+
interval?: number;
|
|
71
|
+
/** Maximum polling time in seconds. Default: 900 */
|
|
72
|
+
timeout?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Full dataflow lifecycle: create → run → poll → delete (always, even on error).
|
|
76
|
+
* Returns the final DataflowResult.
|
|
77
|
+
*/
|
|
78
|
+
export declare function executeDataflow(options: ExecuteDataflowOptions): Promise<DataflowResult>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { HttpError } from "../utils/http.js";
|
|
2
|
+
function buildHeaders(accessToken, businessDomain) {
|
|
3
|
+
return {
|
|
4
|
+
accept: "application/json, text/plain, */*",
|
|
5
|
+
"accept-language": "zh-cn",
|
|
6
|
+
authorization: `Bearer ${accessToken}`,
|
|
7
|
+
token: accessToken,
|
|
8
|
+
"x-business-domain": businessDomain,
|
|
9
|
+
"x-language": "zh-cn",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function debugLog(method, url, headers, body) {
|
|
13
|
+
if (!process.env["KWEAVER_DEBUG_HTTP"])
|
|
14
|
+
return;
|
|
15
|
+
const masked = { ...headers };
|
|
16
|
+
if (masked.authorization)
|
|
17
|
+
masked.authorization = masked.authorization.slice(0, 20) + "…";
|
|
18
|
+
if (masked.token)
|
|
19
|
+
masked.token = masked.token.slice(0, 20) + "…";
|
|
20
|
+
process.stderr.write(`[debug] ${method} ${url}\n`);
|
|
21
|
+
process.stderr.write(`[debug] headers: ${JSON.stringify(masked)}\n`);
|
|
22
|
+
if (body)
|
|
23
|
+
process.stderr.write(`[debug] body (first 300): ${body.slice(0, 300)}\n`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a new dataflow (DAG). Returns the new DAG id.
|
|
27
|
+
*/
|
|
28
|
+
export async function createDataflow(options) {
|
|
29
|
+
const { baseUrl, accessToken, businessDomain = "bd_public", body } = options;
|
|
30
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
31
|
+
const url = `${base}/api/automation/v1/data-flow/flow`;
|
|
32
|
+
const reqHeaders = { ...buildHeaders(accessToken, businessDomain), "content-type": "application/json" };
|
|
33
|
+
const reqBody = JSON.stringify(body);
|
|
34
|
+
debugLog("POST", url, reqHeaders, reqBody);
|
|
35
|
+
const response = await fetch(url, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: reqHeaders,
|
|
38
|
+
body: reqBody,
|
|
39
|
+
});
|
|
40
|
+
const responseBody = await response.text();
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new HttpError(response.status, response.statusText, responseBody);
|
|
43
|
+
}
|
|
44
|
+
const parsed = JSON.parse(responseBody);
|
|
45
|
+
return parsed.id;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Trigger a run for an existing dataflow DAG.
|
|
49
|
+
*/
|
|
50
|
+
export async function runDataflow(options) {
|
|
51
|
+
const { baseUrl, accessToken, businessDomain = "bd_public", dagId } = options;
|
|
52
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
53
|
+
const url = `${base}/api/automation/v1/run-instance/${encodeURIComponent(dagId)}`;
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
...buildHeaders(accessToken, businessDomain),
|
|
58
|
+
"content-type": "application/json",
|
|
59
|
+
},
|
|
60
|
+
body: "{}",
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const responseBody = await response.text();
|
|
64
|
+
throw new HttpError(response.status, response.statusText, responseBody);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Poll GET /api/automation/v1/dag/{dagId}/results until the run is done.
|
|
69
|
+
* Throws on "failed"/"error" status or timeout.
|
|
70
|
+
*/
|
|
71
|
+
export async function pollDataflowResults(options) {
|
|
72
|
+
const { baseUrl, accessToken, businessDomain = "bd_public", dagId, interval = 3, timeout = 900, } = options;
|
|
73
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
74
|
+
const url = `${base}/api/automation/v1/dag/${encodeURIComponent(dagId)}/results`;
|
|
75
|
+
const deadlineMs = Date.now() + timeout * 1000;
|
|
76
|
+
while (Date.now() < deadlineMs) {
|
|
77
|
+
const response = await fetch(url, {
|
|
78
|
+
method: "GET",
|
|
79
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
80
|
+
});
|
|
81
|
+
const responseBody = await response.text();
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new HttpError(response.status, response.statusText, responseBody);
|
|
84
|
+
}
|
|
85
|
+
const parsed = JSON.parse(responseBody);
|
|
86
|
+
const results = parsed.results ?? [];
|
|
87
|
+
const latest = results[0];
|
|
88
|
+
if (latest) {
|
|
89
|
+
if (latest.status === "success" || latest.status === "completed") {
|
|
90
|
+
return latest;
|
|
91
|
+
}
|
|
92
|
+
if (latest.status === "failed" || latest.status === "error") {
|
|
93
|
+
const reason = latest.reason ? `: ${latest.reason}` : "";
|
|
94
|
+
throw new Error(`Dataflow run ${latest.status}${reason}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Still running — wait before next poll
|
|
98
|
+
if (interval > 0) {
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`Dataflow polling timed out after ${timeout}s for DAG ${dagId}`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Delete a dataflow DAG. Best-effort — does not throw on errors.
|
|
106
|
+
*/
|
|
107
|
+
export async function deleteDataflow(options) {
|
|
108
|
+
const { baseUrl, accessToken, businessDomain = "bd_public", dagId } = options;
|
|
109
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
110
|
+
const url = `${base}/api/automation/v1/data-flow/flow/${encodeURIComponent(dagId)}`;
|
|
111
|
+
try {
|
|
112
|
+
await fetch(url, {
|
|
113
|
+
method: "DELETE",
|
|
114
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Best-effort: swallow all errors
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Full dataflow lifecycle: create → run → poll → delete (always, even on error).
|
|
123
|
+
* Returns the final DataflowResult.
|
|
124
|
+
*/
|
|
125
|
+
export async function executeDataflow(options) {
|
|
126
|
+
const { baseUrl, accessToken, businessDomain = "bd_public", body, interval, timeout } = options;
|
|
127
|
+
const dagId = await createDataflow({ baseUrl, accessToken, businessDomain, body });
|
|
128
|
+
try {
|
|
129
|
+
await runDataflow({ baseUrl, accessToken, businessDomain, dagId });
|
|
130
|
+
return await pollDataflowResults({ baseUrl, accessToken, businessDomain, dagId, interval, timeout });
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
await deleteDataflow({ baseUrl, accessToken, businessDomain, dagId }).catch(() => { });
|
|
134
|
+
}
|
|
135
|
+
}
|
package/dist/api/dataviews.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/** Field metadata returned by the data-views API. */
|
|
2
|
+
export interface ViewField {
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
display_name?: string;
|
|
6
|
+
comment?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Normalized data view model (mdl-data-model). */
|
|
9
|
+
export interface DataView {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
query_type: string;
|
|
13
|
+
datasource_id: string;
|
|
14
|
+
fields: ViewField[];
|
|
15
|
+
}
|
|
16
|
+
export declare function parseDataView(raw: Record<string, unknown>): DataView;
|
|
1
17
|
export interface CreateDataViewOptions {
|
|
2
18
|
baseUrl: string;
|
|
3
19
|
accessToken: string;
|
|
@@ -11,10 +27,51 @@ export interface CreateDataViewOptions {
|
|
|
11
27
|
businessDomain?: string;
|
|
12
28
|
}
|
|
13
29
|
export declare function createDataView(options: CreateDataViewOptions): Promise<string>;
|
|
30
|
+
export interface ListDataViewsOptions {
|
|
31
|
+
baseUrl: string;
|
|
32
|
+
accessToken: string;
|
|
33
|
+
businessDomain?: string;
|
|
34
|
+
/** Filter by data source id. */
|
|
35
|
+
datasourceId?: string;
|
|
36
|
+
/** Server-side keyword filter (fuzzy). */
|
|
37
|
+
name?: string;
|
|
38
|
+
/** View type filter (e.g. atomic, custom). */
|
|
39
|
+
type?: string;
|
|
40
|
+
/** Max items; default -1 (all). */
|
|
41
|
+
limit?: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function listDataViews(options: ListDataViewsOptions): Promise<DataView[]>;
|
|
44
|
+
export interface DeleteDataViewOptions {
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
accessToken: string;
|
|
47
|
+
id: string;
|
|
48
|
+
businessDomain?: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function deleteDataView(options: DeleteDataViewOptions): Promise<void>;
|
|
14
51
|
export interface GetDataViewOptions {
|
|
15
52
|
baseUrl: string;
|
|
16
53
|
accessToken: string;
|
|
17
54
|
id: string;
|
|
18
55
|
businessDomain?: string;
|
|
19
56
|
}
|
|
20
|
-
export declare function getDataView(options: GetDataViewOptions): Promise<
|
|
57
|
+
export declare function getDataView(options: GetDataViewOptions): Promise<DataView>;
|
|
58
|
+
export interface FindDataViewOptions {
|
|
59
|
+
baseUrl: string;
|
|
60
|
+
accessToken: string;
|
|
61
|
+
businessDomain?: string;
|
|
62
|
+
/** View name to search for (sent as keyword to server). */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Filter by data source id. */
|
|
65
|
+
datasourceId?: string;
|
|
66
|
+
/** When true, apply client-side exact name match after keyword search (default false). */
|
|
67
|
+
exact?: boolean;
|
|
68
|
+
/** When true, poll until a result appears or timeout (default false). */
|
|
69
|
+
wait?: boolean;
|
|
70
|
+
/** Total wait budget in ms (default 30000). Only used when wait is true. */
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Find data views by name. Uses server-side keyword filtering; when `exact` is true,
|
|
75
|
+
* applies client-side `name ===` filter. Optional polling with exponential backoff.
|
|
76
|
+
*/
|
|
77
|
+
export declare function findDataView(options: FindDataViewOptions): Promise<DataView[]>;
|
package/dist/api/dataviews.js
CHANGED
|
@@ -24,6 +24,41 @@ function extractViewId(data) {
|
|
|
24
24
|
}
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
|
+
export function parseDataView(raw) {
|
|
28
|
+
const fieldsRaw = raw.fields;
|
|
29
|
+
const fields = [];
|
|
30
|
+
if (Array.isArray(fieldsRaw)) {
|
|
31
|
+
for (const f of fieldsRaw) {
|
|
32
|
+
if (f && typeof f === "object") {
|
|
33
|
+
const fr = f;
|
|
34
|
+
fields.push({
|
|
35
|
+
name: String(fr.name ?? ""),
|
|
36
|
+
type: String(fr.type ?? "varchar"),
|
|
37
|
+
display_name: fr.display_name != null ? String(fr.display_name) : undefined,
|
|
38
|
+
comment: fr.comment != null ? String(fr.comment) : undefined,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
id: String(raw.id ?? ""),
|
|
45
|
+
name: String(raw.name ?? ""),
|
|
46
|
+
query_type: String(raw.query_type ?? "SQL"),
|
|
47
|
+
datasource_id: String(raw.data_source_id ?? raw.group_id ?? ""),
|
|
48
|
+
fields,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function extractListPayload(data) {
|
|
52
|
+
if (Array.isArray(data))
|
|
53
|
+
return data;
|
|
54
|
+
if (data && typeof data === "object") {
|
|
55
|
+
const obj = data;
|
|
56
|
+
const items = obj.entries ?? obj.data;
|
|
57
|
+
if (Array.isArray(items))
|
|
58
|
+
return items;
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
27
62
|
export async function createDataView(options) {
|
|
28
63
|
const { baseUrl, accessToken, name, datasourceId, table, fields = [], businessDomain = "bd_public", } = options;
|
|
29
64
|
const viewId = createHash("md5").update(`${datasourceId}:${table}`).digest("hex").slice(0, 35);
|
|
@@ -51,11 +86,90 @@ export async function createDataView(options) {
|
|
|
51
86
|
});
|
|
52
87
|
const responseBody = await response.text();
|
|
53
88
|
if (!response.ok) {
|
|
89
|
+
// If DataView already exists (403 with "Existed" error code), delete and recreate
|
|
90
|
+
if (response.status === 403) {
|
|
91
|
+
try {
|
|
92
|
+
const errBody = JSON.parse(responseBody);
|
|
93
|
+
if (errBody.error_code?.includes("Existed")) {
|
|
94
|
+
const actualId = await findDataViewByName({ baseUrl, accessToken, name, groupId: datasourceId, businessDomain });
|
|
95
|
+
if (actualId && fields.length > 0) {
|
|
96
|
+
// Delete the bare DataView (created by scanMetadata) and recreate with fields
|
|
97
|
+
await deleteDataView({ baseUrl, accessToken, id: actualId, businessDomain });
|
|
98
|
+
const retryResponse = await fetch(url, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { ...buildHeaders(accessToken, businessDomain), "content-type": "application/json" },
|
|
101
|
+
body,
|
|
102
|
+
});
|
|
103
|
+
if (retryResponse.ok) {
|
|
104
|
+
const retryBody = await retryResponse.text();
|
|
105
|
+
const retryId = extractViewId(JSON.parse(retryBody));
|
|
106
|
+
return retryId ?? viewId;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (actualId)
|
|
110
|
+
return actualId;
|
|
111
|
+
return viewId;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { /* fall through to throw */ }
|
|
115
|
+
}
|
|
54
116
|
throw new HttpError(response.status, response.statusText, responseBody);
|
|
55
117
|
}
|
|
56
118
|
const createdId = extractViewId(JSON.parse(responseBody));
|
|
57
119
|
return createdId ?? viewId;
|
|
58
120
|
}
|
|
121
|
+
async function findDataViewByName(options) {
|
|
122
|
+
const list = await listDataViews({
|
|
123
|
+
baseUrl: options.baseUrl,
|
|
124
|
+
accessToken: options.accessToken,
|
|
125
|
+
businessDomain: options.businessDomain,
|
|
126
|
+
name: options.name,
|
|
127
|
+
});
|
|
128
|
+
const match = list.find((e) => e.name === options.name && e.datasource_id === options.groupId);
|
|
129
|
+
return match?.id ?? null;
|
|
130
|
+
}
|
|
131
|
+
export async function listDataViews(options) {
|
|
132
|
+
const { baseUrl, accessToken, businessDomain = "bd_public", datasourceId, name, type, limit = -1, } = options;
|
|
133
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
134
|
+
const url = new URL(`${base}/api/mdl-data-model/v1/data-views`);
|
|
135
|
+
url.searchParams.set("limit", String(limit));
|
|
136
|
+
if (datasourceId)
|
|
137
|
+
url.searchParams.set("data_source_id", datasourceId);
|
|
138
|
+
if (name)
|
|
139
|
+
url.searchParams.set("keyword", name);
|
|
140
|
+
if (type)
|
|
141
|
+
url.searchParams.set("type", type);
|
|
142
|
+
const response = await fetch(url.toString(), {
|
|
143
|
+
method: "GET",
|
|
144
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
145
|
+
});
|
|
146
|
+
const bodyText = await response.text();
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new HttpError(response.status, response.statusText, bodyText);
|
|
149
|
+
}
|
|
150
|
+
const parsed = JSON.parse(bodyText);
|
|
151
|
+
const items = extractListPayload(parsed);
|
|
152
|
+
const out = [];
|
|
153
|
+
for (const item of items) {
|
|
154
|
+
if (item && typeof item === "object") {
|
|
155
|
+
out.push(parseDataView(item));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
export async function deleteDataView(options) {
|
|
161
|
+
const { baseUrl, accessToken, id, businessDomain = "bd_public" } = options;
|
|
162
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
163
|
+
const url = `${base}/api/mdl-data-model/v1/data-views/${encodeURIComponent(id)}`;
|
|
164
|
+
const response = await fetch(url, {
|
|
165
|
+
method: "DELETE",
|
|
166
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
167
|
+
});
|
|
168
|
+
const bodyText = await response.text();
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
throw new HttpError(response.status, response.statusText, bodyText);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
59
173
|
export async function getDataView(options) {
|
|
60
174
|
const { baseUrl, accessToken, id, businessDomain = "bd_public", } = options;
|
|
61
175
|
const base = baseUrl.replace(/\/+$/, "");
|
|
@@ -68,5 +182,40 @@ export async function getDataView(options) {
|
|
|
68
182
|
if (!response.ok) {
|
|
69
183
|
throw new HttpError(response.status, response.statusText, body);
|
|
70
184
|
}
|
|
71
|
-
|
|
185
|
+
let parsed = JSON.parse(body);
|
|
186
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
187
|
+
parsed = parsed[0];
|
|
188
|
+
}
|
|
189
|
+
if (!parsed || typeof parsed !== "object") {
|
|
190
|
+
throw new HttpError(500, "Invalid response", body);
|
|
191
|
+
}
|
|
192
|
+
return parseDataView(parsed);
|
|
193
|
+
}
|
|
194
|
+
function sleepMs(ms) {
|
|
195
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Find data views by name. Uses server-side keyword filtering; when `exact` is true,
|
|
199
|
+
* applies client-side `name ===` filter. Optional polling with exponential backoff.
|
|
200
|
+
*/
|
|
201
|
+
export async function findDataView(options) {
|
|
202
|
+
const { baseUrl, accessToken, businessDomain = "bd_public", name, datasourceId, exact = false, wait = false, timeoutMs = 30_000, } = options;
|
|
203
|
+
const deadline = Date.now() + timeoutMs;
|
|
204
|
+
let attempt = 0;
|
|
205
|
+
while (true) {
|
|
206
|
+
const list = await listDataViews({
|
|
207
|
+
baseUrl,
|
|
208
|
+
accessToken,
|
|
209
|
+
businessDomain,
|
|
210
|
+
datasourceId,
|
|
211
|
+
name,
|
|
212
|
+
limit: -1,
|
|
213
|
+
});
|
|
214
|
+
const results = exact ? list.filter((v) => v.name === name) : list;
|
|
215
|
+
if (results.length > 0 || !wait || Date.now() >= deadline)
|
|
216
|
+
return results;
|
|
217
|
+
const delayMs = Math.min(5000, 1000 * 2 ** attempt);
|
|
218
|
+
attempt += 1;
|
|
219
|
+
await sleepMs(delayMs);
|
|
220
|
+
}
|
|
72
221
|
}
|
package/dist/auth/oauth.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type TokenConfig } from "../config/store.js";
|
|
|
2
2
|
export declare function normalizeBaseUrl(value: string): string;
|
|
3
3
|
/**
|
|
4
4
|
* OAuth2 Authorization Code login flow.
|
|
5
|
-
* 1. Register client (if not already registered)
|
|
5
|
+
* 1. Register client (if not already registered), OR use a provided client ID
|
|
6
6
|
* 2. Open browser to /oauth2/auth
|
|
7
7
|
* 3. Receive authorization code via local HTTP callback
|
|
8
8
|
* 4. Exchange code for access_token + refresh_token
|
|
@@ -11,6 +11,10 @@ export declare function normalizeBaseUrl(value: string): string;
|
|
|
11
11
|
export declare function oauth2Login(baseUrl: string, options?: {
|
|
12
12
|
port?: number;
|
|
13
13
|
scope?: string;
|
|
14
|
+
clientId?: string;
|
|
15
|
+
clientSecret?: string;
|
|
16
|
+
/** Skip TLS certificate verification (self-signed / dev servers only). */
|
|
17
|
+
tlsInsecure?: boolean;
|
|
14
18
|
}): Promise<TokenConfig>;
|
|
15
19
|
/**
|
|
16
20
|
* Playwright-automated OAuth2 login.
|
|
@@ -28,6 +32,7 @@ export declare function playwrightLogin(baseUrl: string, options?: {
|
|
|
28
32
|
password?: string;
|
|
29
33
|
port?: number;
|
|
30
34
|
scope?: string;
|
|
35
|
+
tlsInsecure?: boolean;
|
|
31
36
|
}): Promise<TokenConfig>;
|
|
32
37
|
/**
|
|
33
38
|
* Exchange refresh_token for a new access token (OAuth2 password grant style, same as Python ConfigAuth).
|