@lark-apaas/miaoda-cli 0.1.3-alpha.4bf312e → 0.1.3-alpha.4ec3549
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/dist/api/app/api.js +3 -3
- package/dist/api/app/schemas.js +43 -43
- package/dist/api/db/api.js +132 -112
- package/dist/api/db/client.js +41 -41
- package/dist/api/db/index.js +2 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +5 -5
- package/dist/api/deploy/schemas.js +32 -32
- package/dist/api/file/api.js +74 -87
- package/dist/api/file/client.js +62 -22
- package/dist/api/file/detect.js +3 -3
- package/dist/api/file/parsers.js +18 -7
- package/dist/api/observability/api.js +6 -6
- package/dist/api/observability/schemas.js +14 -14
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +12 -12
- package/dist/cli/commands/db/index.js +268 -163
- package/dist/cli/commands/deploy/index.js +28 -28
- package/dist/cli/commands/file/index.js +81 -61
- package/dist/cli/commands/observability/index.js +69 -69
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +10 -10
- package/dist/cli/handlers/app/update.js +2 -2
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +104 -53
- package/dist/cli/handlers/db/changelog.js +28 -13
- package/dist/cli/handlers/db/data.js +31 -30
- package/dist/cli/handlers/db/migration.js +27 -27
- package/dist/cli/handlers/db/quota.js +3 -3
- package/dist/cli/handlers/db/recovery.js +138 -98
- package/dist/cli/handlers/db/schema.js +33 -33
- package/dist/cli/handlers/db/sql.js +69 -69
- package/dist/cli/handlers/deploy/deploy.js +4 -4
- package/dist/cli/handlers/deploy/error-log.js +1 -1
- package/dist/cli/handlers/deploy/get.js +3 -3
- package/dist/cli/handlers/deploy/polling.js +11 -11
- package/dist/cli/handlers/file/cp.js +30 -30
- package/dist/cli/handlers/file/ls.js +5 -5
- package/dist/cli/handlers/file/quota.js +2 -2
- package/dist/cli/handlers/file/rm.js +24 -24
- package/dist/cli/handlers/file/sign.js +3 -3
- package/dist/cli/handlers/file/stat.js +10 -9
- package/dist/cli/handlers/observability/analytics.js +47 -47
- package/dist/cli/handlers/observability/helpers.js +2 -2
- package/dist/cli/handlers/observability/log.js +9 -9
- package/dist/cli/handlers/observability/metric.js +26 -26
- package/dist/cli/handlers/observability/trace.js +5 -5
- package/dist/cli/handlers/plugin/plugin-local.js +53 -53
- package/dist/cli/handlers/plugin/plugin.js +15 -15
- package/dist/cli/help.js +16 -16
- package/dist/main.js +12 -12
- package/dist/utils/args.js +1 -1
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +9 -9
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +4 -4
- package/dist/utils/http.js +19 -19
- package/dist/utils/output.js +47 -47
- package/dist/utils/poll.js +2 -2
- package/dist/utils/render.js +27 -27
- package/dist/utils/time.js +47 -42
- package/package.json +1 -1
package/dist/api/db/api.js
CHANGED
|
@@ -13,47 +13,34 @@ exports.migrate = migrate;
|
|
|
13
13
|
exports.getMigrationStatus = getMigrationStatus;
|
|
14
14
|
exports.recover = recover;
|
|
15
15
|
exports.getRecoveryPreview = getRecoveryPreview;
|
|
16
|
+
exports.getRecoveryStatus = getRecoveryStatus;
|
|
16
17
|
exports.getDbQuota = getDbQuota;
|
|
17
18
|
const http_1 = require("../../utils/http");
|
|
18
19
|
const error_1 = require("../../utils/error");
|
|
19
20
|
const http_client_1 = require("@lark-apaas/http-client");
|
|
20
21
|
const client_1 = require("./client");
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* 1. 先尝试从 response body 解 envelope,命中 dataloom 业务 code → AppError
|
|
24
|
-
* 2. 兜底返 HttpError,保留真实 status 码与上下文
|
|
25
|
-
*
|
|
26
|
-
* 配合调用点的 try/catch + traceHttp,让 --verbose 在错误路径上也能拿到
|
|
27
|
-
* x-tt-logid 与 status,方便定位线上问题。
|
|
28
|
-
*/
|
|
29
|
-
async function mapDbHttpError(err, url, ctx,
|
|
30
|
-
/**
|
|
31
|
-
* 可选 hook:解析到响应 body 后,如果 envelope 命中业务错误并抛出 AppError,
|
|
32
|
-
* 调用方可以借此把 body 里的额外字段(如 multi-statement 的 partial results)
|
|
33
|
-
* 挂到 AppError 上。
|
|
34
|
-
*/
|
|
35
|
-
onErrorBody) {
|
|
22
|
+
const DEFAULT_TIMEOUT_HINT = 'The server may still be processing your request. Retry the command if needed.';
|
|
23
|
+
async function mapDbHttpError(err, url, ctx, opts) {
|
|
36
24
|
if (err instanceof error_1.AppError)
|
|
37
25
|
throw err;
|
|
38
26
|
// 客户端超时 / abort:SdkHttpError 时 response 通常缺失(status=0),落到 throw 时
|
|
39
|
-
// message 形如 'Failed to recover: 0' 让用户看不懂。识别 abort / timeout
|
|
40
|
-
//
|
|
27
|
+
// message 形如 'Failed to recover: 0' 让用户看不懂。识别 abort / timeout 关键字后
|
|
28
|
+
// 单独抛专用 AppError(带领域可定制的 message + hint),不再包成通用 "Failed to X"。
|
|
41
29
|
if (err instanceof Error) {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
err.name ===
|
|
46
|
-
err.name ===
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
30
|
+
const lower = err.message.toLowerCase();
|
|
31
|
+
if (lower.includes('aborted') ||
|
|
32
|
+
lower.includes('timeout') ||
|
|
33
|
+
err.name === 'AbortError' ||
|
|
34
|
+
err.name === 'TimeoutError') {
|
|
35
|
+
const code = opts?.timeout?.code ?? 'REQUEST_TIMEOUT';
|
|
36
|
+
const message = opts?.timeout?.message ?? 'Request timed out after 30s';
|
|
37
|
+
const hint = opts?.timeout?.hint ?? DEFAULT_TIMEOUT_HINT;
|
|
38
|
+
throw new error_1.AppError(code, message, { next_actions: [hint] });
|
|
52
39
|
}
|
|
53
40
|
}
|
|
54
41
|
if (err instanceof http_client_1.HttpError) {
|
|
55
42
|
const status = err.response?.status ?? 0;
|
|
56
|
-
const statusText = err.response?.statusText ??
|
|
43
|
+
const statusText = err.response?.statusText ?? '';
|
|
57
44
|
try {
|
|
58
45
|
const body = (await err.response?.json());
|
|
59
46
|
if (body) {
|
|
@@ -61,8 +48,8 @@ onErrorBody) {
|
|
|
61
48
|
(0, client_1.extractData)(body); // 业务 code 命中 → 抛 AppError;不命中走兜底
|
|
62
49
|
}
|
|
63
50
|
catch (appErr) {
|
|
64
|
-
if (appErr instanceof error_1.AppError && onErrorBody) {
|
|
65
|
-
onErrorBody(body, appErr);
|
|
51
|
+
if (appErr instanceof error_1.AppError && opts?.onErrorBody) {
|
|
52
|
+
opts.onErrorBody(body, appErr);
|
|
66
53
|
}
|
|
67
54
|
throw appErr;
|
|
68
55
|
}
|
|
@@ -105,18 +92,28 @@ function attachSqlPartialResults(body, appErr) {
|
|
|
105
92
|
*/
|
|
106
93
|
async function execSql(opts) {
|
|
107
94
|
const client = (0, http_1.getHttpClient)();
|
|
108
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
95
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/sql', {
|
|
109
96
|
dbBranch: opts.dbBranch,
|
|
110
97
|
});
|
|
111
98
|
const start = Date.now();
|
|
112
99
|
let response;
|
|
113
100
|
try {
|
|
114
101
|
response = await client.post(url, { sql: opts.sql });
|
|
115
|
-
(0, client_1.traceHttp)(
|
|
102
|
+
(0, client_1.traceHttp)('POST', url, start, response);
|
|
116
103
|
}
|
|
117
104
|
catch (err) {
|
|
118
|
-
(0, client_1.traceHttp)(
|
|
119
|
-
await mapDbHttpError(err, url,
|
|
105
|
+
(0, client_1.traceHttp)('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
106
|
+
await mapDbHttpError(err, url, 'Failed to execute SQL', {
|
|
107
|
+
onErrorBody: attachSqlPartialResults,
|
|
108
|
+
// SQL 路径单独的超时文案:PG 的 statement_timeout 会回滚整条事务,所以这里
|
|
109
|
+
// 明示「事务已回滚、没有改动落地」,并把 hint 引导到「简化 SQL / 加 LIMIT / 拆条」。
|
|
110
|
+
timeout: {
|
|
111
|
+
code: 'SQL_EXECUTION_TIMEOUT',
|
|
112
|
+
message: 'SQL execution timed out after 30s',
|
|
113
|
+
hint: 'The transaction was rolled back; no changes were applied. ' +
|
|
114
|
+
'Simplify the SQL, add filters or LIMIT for queries, or split it into smaller statements.',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
120
117
|
throw err; // 不可达
|
|
121
118
|
}
|
|
122
119
|
const body = (await response.json());
|
|
@@ -127,11 +124,11 @@ async function execSql(opts) {
|
|
|
127
124
|
// results 末尾追加一条 SqlType="ERROR" 的哨兵,data 字段是 {code,message} JSON。
|
|
128
125
|
// 这样网关在错误路径不会吞业务字段,CLI 拿到完整 partial_results + statement_index。
|
|
129
126
|
const last = results.at(-1);
|
|
130
|
-
if (last?.sqlType ===
|
|
127
|
+
if (last?.sqlType === 'ERROR') {
|
|
131
128
|
const appErr = parseSqlErrorSentinel(last.data);
|
|
132
129
|
appErr.partial_results = results.slice(0, -1);
|
|
133
130
|
const stmtIdx = data.errorStatementIndex;
|
|
134
|
-
if (typeof stmtIdx ===
|
|
131
|
+
if (typeof stmtIdx === 'number')
|
|
135
132
|
appErr.statement_index = stmtIdx;
|
|
136
133
|
throw appErr;
|
|
137
134
|
}
|
|
@@ -163,12 +160,12 @@ async function execSql(opts) {
|
|
|
163
160
|
function parseSqlErrorSentinel(payload) {
|
|
164
161
|
try {
|
|
165
162
|
const parsed = JSON.parse(payload);
|
|
166
|
-
const code = typeof parsed.code ===
|
|
167
|
-
const message = typeof parsed.message ===
|
|
163
|
+
const code = typeof parsed.code === 'string' && parsed.code !== '' ? parsed.code : 'INTERNAL_DB_ERROR';
|
|
164
|
+
const message = typeof parsed.message === 'string' ? parsed.message : payload;
|
|
168
165
|
return (0, client_1.mapDataloomBizError)(code, message);
|
|
169
166
|
}
|
|
170
167
|
catch {
|
|
171
|
-
return new error_1.AppError(
|
|
168
|
+
return new error_1.AppError('INTERNAL_DB_ERROR', payload);
|
|
172
169
|
}
|
|
173
170
|
}
|
|
174
171
|
// ── db schema → InnerGetSchema ──
|
|
@@ -180,21 +177,21 @@ function parseSqlErrorSentinel(payload) {
|
|
|
180
177
|
*/
|
|
181
178
|
async function getSchema(opts) {
|
|
182
179
|
const client = (0, http_1.getHttpClient)();
|
|
183
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
184
|
-
format: opts.format ??
|
|
180
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/schema', {
|
|
181
|
+
format: opts.format ?? 'schema',
|
|
185
182
|
tableNames: opts.tableNames,
|
|
186
|
-
includeStats: opts.includeStats ?
|
|
183
|
+
includeStats: opts.includeStats ? 'true' : undefined,
|
|
187
184
|
dbBranch: opts.dbBranch,
|
|
188
185
|
});
|
|
189
186
|
const start = Date.now();
|
|
190
187
|
let response;
|
|
191
188
|
try {
|
|
192
189
|
response = await client.get(url);
|
|
193
|
-
(0, client_1.traceHttp)(
|
|
190
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
194
191
|
}
|
|
195
192
|
catch (err) {
|
|
196
|
-
(0, client_1.traceHttp)(
|
|
197
|
-
await mapDbHttpError(err, url,
|
|
193
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
194
|
+
await mapDbHttpError(err, url, 'Failed to get schema');
|
|
198
195
|
throw err; // 不可达
|
|
199
196
|
}
|
|
200
197
|
const body = (await response.json());
|
|
@@ -214,24 +211,24 @@ async function getSchema(opts) {
|
|
|
214
211
|
*/
|
|
215
212
|
async function importData(opts) {
|
|
216
213
|
const client = (0, http_1.getHttpClient)();
|
|
217
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
214
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/data/import');
|
|
218
215
|
const reqBody = {
|
|
219
216
|
tableName: opts.tableName,
|
|
220
217
|
format: opts.format,
|
|
221
|
-
records: opts.body.toString(
|
|
218
|
+
records: opts.body.toString('utf8'),
|
|
222
219
|
};
|
|
223
|
-
if (opts.dbBranch !== undefined && opts.dbBranch !==
|
|
220
|
+
if (opts.dbBranch !== undefined && opts.dbBranch !== '') {
|
|
224
221
|
reqBody.dbBranch = opts.dbBranch;
|
|
225
222
|
}
|
|
226
223
|
const start = Date.now();
|
|
227
224
|
let response;
|
|
228
225
|
try {
|
|
229
226
|
response = await client.post(url, reqBody);
|
|
230
|
-
(0, client_1.traceHttp)(
|
|
227
|
+
(0, client_1.traceHttp)('POST', url, start, response);
|
|
231
228
|
}
|
|
232
229
|
catch (err) {
|
|
233
|
-
(0, client_1.traceHttp)(
|
|
234
|
-
await mapDbHttpError(err, url,
|
|
230
|
+
(0, client_1.traceHttp)('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
231
|
+
await mapDbHttpError(err, url, 'Failed to import data');
|
|
235
232
|
throw err; // 不可达
|
|
236
233
|
}
|
|
237
234
|
// 后端 InnerAdminImportData 响应里 data 直接返 {tableName, recordCount, durationMs}
|
|
@@ -255,7 +252,7 @@ async function importData(opts) {
|
|
|
255
252
|
*/
|
|
256
253
|
async function exportData(opts) {
|
|
257
254
|
const client = (0, http_1.getHttpClient)();
|
|
258
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
255
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/data/export', {
|
|
259
256
|
tableName: opts.tableName,
|
|
260
257
|
format: opts.format,
|
|
261
258
|
limit: String(opts.limit ?? 5000),
|
|
@@ -265,35 +262,35 @@ async function exportData(opts) {
|
|
|
265
262
|
const start = Date.now();
|
|
266
263
|
let response;
|
|
267
264
|
try {
|
|
268
|
-
response = await client.request({ method:
|
|
269
|
-
(0, client_1.traceHttp)(
|
|
265
|
+
response = await client.request({ method: 'POST', url });
|
|
266
|
+
(0, client_1.traceHttp)('POST', url, start, response);
|
|
270
267
|
}
|
|
271
268
|
catch (err) {
|
|
272
|
-
(0, client_1.traceHttp)(
|
|
273
|
-
await mapDbHttpError(err, url,
|
|
269
|
+
(0, client_1.traceHttp)('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
270
|
+
await mapDbHttpError(err, url, 'Failed to export data');
|
|
274
271
|
throw err; // 不可达
|
|
275
272
|
}
|
|
276
273
|
// 成功路径:响应 body 通常是原始 CSV/SQL/JSON 字节,但部分错误场景下网关会返
|
|
277
274
|
// HTTP 200 + JSON envelope(status_code != "0"),需要在这里嗅探兜底。
|
|
278
275
|
const defaultContentType = {
|
|
279
|
-
csv:
|
|
280
|
-
sql:
|
|
281
|
-
json:
|
|
276
|
+
csv: 'text/csv',
|
|
277
|
+
sql: 'text/plain',
|
|
278
|
+
json: 'application/json',
|
|
282
279
|
};
|
|
283
|
-
const contentType = response.headers.get(
|
|
280
|
+
const contentType = response.headers.get('Content-Type') ?? defaultContentType[opts.format];
|
|
284
281
|
const ab = await response.arrayBuffer();
|
|
285
282
|
const buf = Buffer.from(new Uint8Array(ab));
|
|
286
283
|
if (buf.length === 0) {
|
|
287
|
-
throw new error_1.AppError(
|
|
284
|
+
throw new error_1.AppError('INTERNAL_DB_ERROR', 'Empty export response body');
|
|
288
285
|
}
|
|
289
286
|
// Envelope sniff:HTTP 200 + Content-Type 为 application/json + body 解析得到
|
|
290
287
|
// InnerEnvelope 且 status_code 非 "0" 时,按业务错误抛出。
|
|
291
288
|
// CSV / SQL 都是非 JSON 输出,application/json 响应必是错误信封;JSON 格式
|
|
292
289
|
// 成功响应自身就是 application/json,跳过 sniff 避免误判。
|
|
293
|
-
if (opts.format !==
|
|
290
|
+
if (opts.format !== 'json' && /application\/json/i.test(contentType)) {
|
|
294
291
|
try {
|
|
295
|
-
const parsed = JSON.parse(buf.toString(
|
|
296
|
-
if (parsed.status_code != null && parsed.status_code !==
|
|
292
|
+
const parsed = JSON.parse(buf.toString('utf8'));
|
|
293
|
+
if (parsed.status_code != null && parsed.status_code !== '0') {
|
|
297
294
|
// 复用 extractData 的错误映射逻辑(throw AppError)
|
|
298
295
|
(0, client_1.extractData)(parsed);
|
|
299
296
|
}
|
|
@@ -306,7 +303,7 @@ async function exportData(opts) {
|
|
|
306
303
|
}
|
|
307
304
|
}
|
|
308
305
|
// 后端通过响应头回传记录数(避免污染 body);header 缺失或解析失败 → undefined
|
|
309
|
-
const recordCountHeader = response.headers.get(
|
|
306
|
+
const recordCountHeader = response.headers.get('X-Miaoda-Record-Count');
|
|
310
307
|
const parsedCount = recordCountHeader != null ? Number(recordCountHeader) : NaN;
|
|
311
308
|
const recordCount = Number.isFinite(parsedCount) && parsedCount >= 0 ? parsedCount : undefined;
|
|
312
309
|
return {
|
|
@@ -325,7 +322,7 @@ async function exportData(opts) {
|
|
|
325
322
|
*/
|
|
326
323
|
async function listDDLChangelog(opts) {
|
|
327
324
|
const client = (0, http_1.getHttpClient)();
|
|
328
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
325
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/changelog', {
|
|
329
326
|
table: opts.table,
|
|
330
327
|
since: opts.since,
|
|
331
328
|
until: opts.until,
|
|
@@ -337,18 +334,18 @@ async function listDDLChangelog(opts) {
|
|
|
337
334
|
let response;
|
|
338
335
|
try {
|
|
339
336
|
response = await client.get(url);
|
|
340
|
-
(0, client_1.traceHttp)(
|
|
337
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
341
338
|
}
|
|
342
339
|
catch (err) {
|
|
343
|
-
(0, client_1.traceHttp)(
|
|
344
|
-
await mapDbHttpError(err, url,
|
|
340
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
341
|
+
await mapDbHttpError(err, url, 'Failed to list DDL changelog');
|
|
345
342
|
throw err; // 不可达
|
|
346
343
|
}
|
|
347
344
|
const body = (await response.json());
|
|
348
345
|
const data = (0, client_1.extractData)(body);
|
|
349
346
|
return {
|
|
350
347
|
items: data.items ?? [],
|
|
351
|
-
nextCursor: data.nextCursor && data.nextCursor !==
|
|
348
|
+
nextCursor: data.nextCursor && data.nextCursor !== '' ? data.nextCursor : null,
|
|
352
349
|
hasMore: Boolean(data.hasMore),
|
|
353
350
|
};
|
|
354
351
|
}
|
|
@@ -359,7 +356,7 @@ async function listDDLChangelog(opts) {
|
|
|
359
356
|
*/
|
|
360
357
|
async function getAuditStatus(opts) {
|
|
361
358
|
const client = (0, http_1.getHttpClient)();
|
|
362
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
359
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/audit/status', {
|
|
363
360
|
table: opts.table,
|
|
364
361
|
dbBranch: opts.dbBranch,
|
|
365
362
|
});
|
|
@@ -367,11 +364,11 @@ async function getAuditStatus(opts) {
|
|
|
367
364
|
let response;
|
|
368
365
|
try {
|
|
369
366
|
response = await client.get(url);
|
|
370
|
-
(0, client_1.traceHttp)(
|
|
367
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
371
368
|
}
|
|
372
369
|
catch (err) {
|
|
373
|
-
(0, client_1.traceHttp)(
|
|
374
|
-
await mapDbHttpError(err, url,
|
|
370
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
371
|
+
await mapDbHttpError(err, url, 'Failed to get audit status');
|
|
375
372
|
throw err; // 不可达
|
|
376
373
|
}
|
|
377
374
|
const respBody = (await response.json());
|
|
@@ -384,30 +381,30 @@ async function getAuditStatus(opts) {
|
|
|
384
381
|
*/
|
|
385
382
|
async function setAuditConfig(opts) {
|
|
386
383
|
const client = (0, http_1.getHttpClient)();
|
|
387
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
384
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/audit/config');
|
|
388
385
|
const body = {
|
|
389
386
|
table: opts.table,
|
|
390
387
|
enabled: opts.enabled,
|
|
391
388
|
};
|
|
392
|
-
if (opts.retention !== undefined && opts.retention !==
|
|
389
|
+
if (opts.retention !== undefined && opts.retention !== '')
|
|
393
390
|
body.retention = opts.retention;
|
|
394
|
-
if (opts.dbBranch !== undefined && opts.dbBranch !==
|
|
391
|
+
if (opts.dbBranch !== undefined && opts.dbBranch !== '')
|
|
395
392
|
body.dbBranch = opts.dbBranch;
|
|
396
393
|
const start = Date.now();
|
|
397
394
|
let response;
|
|
398
395
|
try {
|
|
399
396
|
response = await client.post(url, body);
|
|
400
|
-
(0, client_1.traceHttp)(
|
|
397
|
+
(0, client_1.traceHttp)('POST', url, start, response);
|
|
401
398
|
}
|
|
402
399
|
catch (err) {
|
|
403
|
-
(0, client_1.traceHttp)(
|
|
404
|
-
await mapDbHttpError(err, url,
|
|
400
|
+
(0, client_1.traceHttp)('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
401
|
+
await mapDbHttpError(err, url, 'Failed to set audit config');
|
|
405
402
|
throw err; // 不可达
|
|
406
403
|
}
|
|
407
404
|
const respBody = (await response.json());
|
|
408
405
|
const data = (0, client_1.extractData)(respBody);
|
|
409
406
|
if (!data.status) {
|
|
410
|
-
throw new error_1.AppError(
|
|
407
|
+
throw new error_1.AppError('INTERNAL_DB_ERROR', 'audit config response missing status field');
|
|
411
408
|
}
|
|
412
409
|
return data.status;
|
|
413
410
|
}
|
|
@@ -425,11 +422,11 @@ async function setAuditConfig(opts) {
|
|
|
425
422
|
*/
|
|
426
423
|
async function listAuditLog(opts) {
|
|
427
424
|
if (opts.tables.length === 0) {
|
|
428
|
-
throw new error_1.AppError(
|
|
425
|
+
throw new error_1.AppError('ARGS_INVALID', 'at least one table is required');
|
|
429
426
|
}
|
|
430
427
|
const client = (0, http_1.getHttpClient)();
|
|
431
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
432
|
-
tables: opts.tables.join(
|
|
428
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/audit/log', {
|
|
429
|
+
tables: opts.tables.join(','),
|
|
433
430
|
since: opts.since,
|
|
434
431
|
until: opts.until,
|
|
435
432
|
limit: opts.limit !== undefined ? String(opts.limit) : undefined,
|
|
@@ -440,18 +437,18 @@ async function listAuditLog(opts) {
|
|
|
440
437
|
let response;
|
|
441
438
|
try {
|
|
442
439
|
response = await client.get(url);
|
|
443
|
-
(0, client_1.traceHttp)(
|
|
440
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
444
441
|
}
|
|
445
442
|
catch (err) {
|
|
446
|
-
(0, client_1.traceHttp)(
|
|
447
|
-
await mapDbHttpError(err, url,
|
|
443
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
444
|
+
await mapDbHttpError(err, url, 'Failed to list audit log');
|
|
448
445
|
throw err; // 不可达
|
|
449
446
|
}
|
|
450
447
|
const body = (await response.json());
|
|
451
448
|
const data = (0, client_1.extractData)(body);
|
|
452
449
|
return {
|
|
453
450
|
items: data.items ?? [],
|
|
454
|
-
nextCursor: data.nextCursor && data.nextCursor !==
|
|
451
|
+
nextCursor: data.nextCursor && data.nextCursor !== '' ? data.nextCursor : null,
|
|
455
452
|
hasMore: Boolean(data.hasMore),
|
|
456
453
|
skipped: data.skipped ?? [],
|
|
457
454
|
};
|
|
@@ -463,7 +460,7 @@ async function listAuditLog(opts) {
|
|
|
463
460
|
*/
|
|
464
461
|
async function migrationInit(opts) {
|
|
465
462
|
const client = (0, http_1.getHttpClient)();
|
|
466
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
463
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/enableMultiEnv');
|
|
467
464
|
const body = {};
|
|
468
465
|
if (opts.syncData !== undefined)
|
|
469
466
|
body.syncData = opts.syncData;
|
|
@@ -471,11 +468,11 @@ async function migrationInit(opts) {
|
|
|
471
468
|
let response;
|
|
472
469
|
try {
|
|
473
470
|
response = await client.post(url, body);
|
|
474
|
-
(0, client_1.traceHttp)(
|
|
471
|
+
(0, client_1.traceHttp)('POST', url, start, response);
|
|
475
472
|
}
|
|
476
473
|
catch (err) {
|
|
477
|
-
(0, client_1.traceHttp)(
|
|
478
|
-
await mapDbHttpError(err, url,
|
|
474
|
+
(0, client_1.traceHttp)('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
475
|
+
await mapDbHttpError(err, url, 'Failed to init migration');
|
|
479
476
|
throw err; // 不可达
|
|
480
477
|
}
|
|
481
478
|
const respBody = (await response.json());
|
|
@@ -487,16 +484,16 @@ async function migrationInit(opts) {
|
|
|
487
484
|
*/
|
|
488
485
|
async function migrate(opts) {
|
|
489
486
|
const client = (0, http_1.getHttpClient)();
|
|
490
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
487
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/migration');
|
|
491
488
|
const start = Date.now();
|
|
492
489
|
let response;
|
|
493
490
|
try {
|
|
494
491
|
response = await client.post(url, { dryRun: opts.dryRun });
|
|
495
|
-
(0, client_1.traceHttp)(
|
|
492
|
+
(0, client_1.traceHttp)('POST', url, start, response);
|
|
496
493
|
}
|
|
497
494
|
catch (err) {
|
|
498
|
-
(0, client_1.traceHttp)(
|
|
499
|
-
await mapDbHttpError(err, url,
|
|
495
|
+
(0, client_1.traceHttp)('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
496
|
+
await mapDbHttpError(err, url, 'Failed to migrate');
|
|
500
497
|
throw err; // 不可达
|
|
501
498
|
}
|
|
502
499
|
const respBody = (await response.json());
|
|
@@ -509,7 +506,7 @@ async function migrate(opts) {
|
|
|
509
506
|
*/
|
|
510
507
|
async function getMigrationStatus(opts) {
|
|
511
508
|
const client = (0, http_1.getHttpClient)();
|
|
512
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
509
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/migration/status', {
|
|
513
510
|
taskId: opts.taskId,
|
|
514
511
|
dbBranch: opts.dbBranch,
|
|
515
512
|
});
|
|
@@ -517,11 +514,11 @@ async function getMigrationStatus(opts) {
|
|
|
517
514
|
let response;
|
|
518
515
|
try {
|
|
519
516
|
response = await client.get(url);
|
|
520
|
-
(0, client_1.traceHttp)(
|
|
517
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
521
518
|
}
|
|
522
519
|
catch (err) {
|
|
523
|
-
(0, client_1.traceHttp)(
|
|
524
|
-
await mapDbHttpError(err, url,
|
|
520
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
521
|
+
await mapDbHttpError(err, url, 'Failed to get migration status');
|
|
525
522
|
throw err; // 不可达
|
|
526
523
|
}
|
|
527
524
|
const body = (await response.json());
|
|
@@ -534,7 +531,7 @@ async function getMigrationStatus(opts) {
|
|
|
534
531
|
*/
|
|
535
532
|
async function recover(opts) {
|
|
536
533
|
const client = (0, http_1.getHttpClient)();
|
|
537
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
534
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/recovery');
|
|
538
535
|
const start = Date.now();
|
|
539
536
|
let response;
|
|
540
537
|
try {
|
|
@@ -542,11 +539,11 @@ async function recover(opts) {
|
|
|
542
539
|
target: opts.target,
|
|
543
540
|
dryRun: opts.dryRun,
|
|
544
541
|
});
|
|
545
|
-
(0, client_1.traceHttp)(
|
|
542
|
+
(0, client_1.traceHttp)('POST', url, start, response);
|
|
546
543
|
}
|
|
547
544
|
catch (err) {
|
|
548
|
-
(0, client_1.traceHttp)(
|
|
549
|
-
await mapDbHttpError(err, url,
|
|
545
|
+
(0, client_1.traceHttp)('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
546
|
+
await mapDbHttpError(err, url, 'Failed to recover');
|
|
550
547
|
throw err; // 不可达
|
|
551
548
|
}
|
|
552
549
|
const respBody = (await response.json());
|
|
@@ -558,7 +555,7 @@ async function recover(opts) {
|
|
|
558
555
|
*/
|
|
559
556
|
async function getRecoveryPreview(opts) {
|
|
560
557
|
const client = (0, http_1.getHttpClient)();
|
|
561
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
558
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/recovery/preview', {
|
|
562
559
|
previewRequestId: opts.previewRequestId,
|
|
563
560
|
dbBranch: opts.dbBranch,
|
|
564
561
|
});
|
|
@@ -566,11 +563,34 @@ async function getRecoveryPreview(opts) {
|
|
|
566
563
|
let response;
|
|
567
564
|
try {
|
|
568
565
|
response = await client.get(url);
|
|
569
|
-
(0, client_1.traceHttp)(
|
|
566
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
570
|
+
await mapDbHttpError(err, url, 'Failed to get recovery preview');
|
|
571
|
+
throw err; // 不可达
|
|
572
|
+
}
|
|
573
|
+
const body = (await response.json());
|
|
574
|
+
return (0, client_1.extractData)(body);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* 后端:GET /v1/dataloom/app/{appId}/db/recovery/status
|
|
578
|
+
* CLI apply 触发后定时调本接口直到 status=success/failed。dataloom 内部 Redis
|
|
579
|
+
* 维护 workspace 级 restore 状态,无需传 task id;workspace+dbBranch 维度同时
|
|
580
|
+
* 只允许一个 restore 进行中。
|
|
581
|
+
*/
|
|
582
|
+
async function getRecoveryStatus(opts) {
|
|
583
|
+
const client = (0, http_1.getHttpClient)();
|
|
584
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/recovery/status', { dbBranch: opts.dbBranch });
|
|
585
|
+
const start = Date.now();
|
|
586
|
+
let response;
|
|
587
|
+
try {
|
|
588
|
+
response = await client.get(url);
|
|
589
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
570
590
|
}
|
|
571
591
|
catch (err) {
|
|
572
|
-
(0, client_1.traceHttp)(
|
|
573
|
-
await mapDbHttpError(err, url,
|
|
592
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
593
|
+
await mapDbHttpError(err, url, 'Failed to get recovery status');
|
|
574
594
|
throw err; // 不可达
|
|
575
595
|
}
|
|
576
596
|
const body = (await response.json());
|
|
@@ -582,16 +602,16 @@ async function getRecoveryPreview(opts) {
|
|
|
582
602
|
*/
|
|
583
603
|
async function getDbQuota(opts) {
|
|
584
604
|
const client = (0, http_1.getHttpClient)();
|
|
585
|
-
const url = (0, client_1.buildInnerUrl)(opts.appId,
|
|
605
|
+
const url = (0, client_1.buildInnerUrl)(opts.appId, '/db/quota', { dbBranch: opts.dbBranch });
|
|
586
606
|
const start = Date.now();
|
|
587
607
|
let response;
|
|
588
608
|
try {
|
|
589
609
|
response = await client.get(url);
|
|
590
|
-
(0, client_1.traceHttp)(
|
|
610
|
+
(0, client_1.traceHttp)('GET', url, start, response);
|
|
591
611
|
}
|
|
592
612
|
catch (err) {
|
|
593
|
-
(0, client_1.traceHttp)(
|
|
594
|
-
await mapDbHttpError(err, url,
|
|
613
|
+
(0, client_1.traceHttp)('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
614
|
+
await mapDbHttpError(err, url, 'Failed to get db quota');
|
|
595
615
|
throw err; // 不可达
|
|
596
616
|
}
|
|
597
617
|
const respBody = (await response.json());
|