postgresai 0.14.0-dev.51 → 0.14.0-dev.52
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/bin/postgres-ai.ts +0 -324
- package/bun.lock +1 -3
- package/dist/bin/postgres-ai.js +171 -1712
- package/dist/sql/01.role.sql +16 -0
- package/dist/sql/02.permissions.sql +37 -0
- package/dist/sql/03.optional_rds.sql +6 -0
- package/dist/sql/04.optional_self_managed.sql +8 -0
- package/dist/sql/05.helpers.sql +415 -0
- package/lib/auth-server.ts +75 -65
- package/lib/config.ts +0 -3
- package/package.json +2 -4
- package/test/init.integration.test.ts +6 -6
- package/lib/checkup-api.ts +0 -175
- package/lib/checkup.ts +0 -1141
- package/lib/metrics-loader.ts +0 -514
- package/test/checkup.test.ts +0 -1016
- package/test/schema-validation.test.ts +0 -260
package/lib/auth-server.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as http from "http";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* OAuth callback result
|
|
3
5
|
*/
|
|
@@ -31,7 +33,7 @@ function escapeHtml(str: string | null): string {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
|
-
* Create and start callback server using
|
|
36
|
+
* Create and start callback server using Node.js http module
|
|
35
37
|
*
|
|
36
38
|
* @param port - Port to listen on (0 for random available port)
|
|
37
39
|
* @param expectedState - Expected state parameter for CSRF protection
|
|
@@ -53,53 +55,60 @@ export function createCallbackServer(
|
|
|
53
55
|
let actualPort = port;
|
|
54
56
|
let resolveCallback: (value: CallbackResult) => void;
|
|
55
57
|
let rejectCallback: (reason: Error) => void;
|
|
56
|
-
let serverInstance:
|
|
58
|
+
let serverInstance: http.Server | null = null;
|
|
57
59
|
|
|
58
60
|
const promise = new Promise<CallbackResult>((resolve, reject) => {
|
|
59
61
|
resolveCallback = resolve;
|
|
60
62
|
rejectCallback = reject;
|
|
61
63
|
});
|
|
62
64
|
|
|
65
|
+
const stopServer = () => {
|
|
66
|
+
if (serverInstance) {
|
|
67
|
+
serverInstance.close();
|
|
68
|
+
serverInstance = null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
63
72
|
// Timeout handler
|
|
64
73
|
const timeout = setTimeout(() => {
|
|
65
74
|
if (!resolved) {
|
|
66
75
|
resolved = true;
|
|
67
|
-
|
|
68
|
-
serverInstance.stop();
|
|
69
|
-
}
|
|
76
|
+
stopServer();
|
|
70
77
|
rejectCallback(new Error("Authentication timeout. Please try again."));
|
|
71
78
|
}
|
|
72
79
|
}, timeoutMs);
|
|
73
80
|
|
|
74
|
-
serverInstance =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
+
serverInstance = http.createServer((req, res) => {
|
|
82
|
+
if (resolved) {
|
|
83
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
84
|
+
res.end("Already handled");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
81
87
|
|
|
82
|
-
|
|
88
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${actualPort}`);
|
|
83
89
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
// Only handle /callback path
|
|
91
|
+
if (!url.pathname.startsWith("/callback")) {
|
|
92
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
93
|
+
res.end("Not Found");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
88
96
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
const code = url.searchParams.get("code");
|
|
98
|
+
const state = url.searchParams.get("state");
|
|
99
|
+
const error = url.searchParams.get("error");
|
|
100
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
// Handle OAuth error
|
|
103
|
+
if (error) {
|
|
104
|
+
resolved = true;
|
|
105
|
+
clearTimeout(timeout);
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
setTimeout(() => stopServer(), 100);
|
|
108
|
+
rejectCallback(new Error(`OAuth error: ${error}${errorDescription ? ` - ${errorDescription}` : ""}`));
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
111
|
+
res.end(`
|
|
103
112
|
<!DOCTYPE html>
|
|
104
113
|
<html>
|
|
105
114
|
<head>
|
|
@@ -120,12 +129,14 @@ export function createCallbackServer(
|
|
|
120
129
|
</div>
|
|
121
130
|
</body>
|
|
122
131
|
</html>
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
125
135
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
// Validate required parameters
|
|
137
|
+
if (!code || !state) {
|
|
138
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
139
|
+
res.end(`
|
|
129
140
|
<!DOCTYPE html>
|
|
130
141
|
<html>
|
|
131
142
|
<head>
|
|
@@ -144,18 +155,20 @@ export function createCallbackServer(
|
|
|
144
155
|
</div>
|
|
145
156
|
</body>
|
|
146
157
|
</html>
|
|
147
|
-
|
|
148
|
-
|
|
158
|
+
`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
149
161
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
// Validate state (CSRF protection)
|
|
163
|
+
if (expectedState && state !== expectedState) {
|
|
164
|
+
resolved = true;
|
|
165
|
+
clearTimeout(timeout);
|
|
154
166
|
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
setTimeout(() => stopServer(), 100);
|
|
168
|
+
rejectCallback(new Error("State mismatch (possible CSRF attack)"));
|
|
157
169
|
|
|
158
|
-
|
|
170
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
171
|
+
res.end(`
|
|
159
172
|
<!DOCTYPE html>
|
|
160
173
|
<html>
|
|
161
174
|
<head>
|
|
@@ -174,19 +187,21 @@ export function createCallbackServer(
|
|
|
174
187
|
</div>
|
|
175
188
|
</body>
|
|
176
189
|
</html>
|
|
177
|
-
|
|
178
|
-
|
|
190
|
+
`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
179
193
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
194
|
+
// Success!
|
|
195
|
+
resolved = true;
|
|
196
|
+
clearTimeout(timeout);
|
|
183
197
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
// Resolve first, then stop server asynchronously after response is sent.
|
|
199
|
+
// The 100ms delay ensures the HTTP response is fully written before closing.
|
|
200
|
+
resolveCallback({ code, state });
|
|
201
|
+
setTimeout(() => stopServer(), 100);
|
|
188
202
|
|
|
189
|
-
|
|
203
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
204
|
+
res.end(`
|
|
190
205
|
<!DOCTYPE html>
|
|
191
206
|
<html>
|
|
192
207
|
<head>
|
|
@@ -205,24 +220,19 @@ export function createCallbackServer(
|
|
|
205
220
|
</div>
|
|
206
221
|
</body>
|
|
207
222
|
</html>
|
|
208
|
-
|
|
209
|
-
},
|
|
223
|
+
`);
|
|
210
224
|
});
|
|
211
225
|
|
|
212
|
-
|
|
226
|
+
serverInstance.listen(port, "127.0.0.1", () => {
|
|
227
|
+
const address = serverInstance?.address();
|
|
228
|
+
if (address && typeof address === "object") {
|
|
229
|
+
actualPort = address.port;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
213
232
|
|
|
214
233
|
return {
|
|
215
|
-
server: { stop:
|
|
234
|
+
server: { stop: stopServer },
|
|
216
235
|
promise,
|
|
217
236
|
getPort: () => actualPort,
|
|
218
237
|
};
|
|
219
238
|
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get the actual port the server is listening on
|
|
223
|
-
* @param server - Bun server instance
|
|
224
|
-
* @returns Port number
|
|
225
|
-
*/
|
|
226
|
-
export function getServerPort(server: ReturnType<typeof Bun.serve>): number {
|
|
227
|
-
return server.port;
|
|
228
|
-
}
|
package/lib/config.ts
CHANGED
|
@@ -9,7 +9,6 @@ export interface Config {
|
|
|
9
9
|
apiKey: string | null;
|
|
10
10
|
baseUrl: string | null;
|
|
11
11
|
orgId: number | null;
|
|
12
|
-
defaultProject: string | null;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -47,7 +46,6 @@ export function readConfig(): Config {
|
|
|
47
46
|
apiKey: null,
|
|
48
47
|
baseUrl: null,
|
|
49
48
|
orgId: null,
|
|
50
|
-
defaultProject: null,
|
|
51
49
|
};
|
|
52
50
|
|
|
53
51
|
// Try user-level config first
|
|
@@ -59,7 +57,6 @@ export function readConfig(): Config {
|
|
|
59
57
|
config.apiKey = parsed.apiKey || null;
|
|
60
58
|
config.baseUrl = parsed.baseUrl || null;
|
|
61
59
|
config.orgId = parsed.orgId || null;
|
|
62
|
-
config.defaultProject = parsed.defaultProject || null;
|
|
63
60
|
return config;
|
|
64
61
|
} catch (err) {
|
|
65
62
|
const message = err instanceof Error ? err.message : String(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postgresai",
|
|
3
|
-
"version": "0.14.0-dev.
|
|
3
|
+
"version": "0.14.0-dev.52",
|
|
4
4
|
"description": "postgres_ai CLI",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"private": false,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">=18"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
|
-
"build": "bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e \"const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))\"",
|
|
25
|
+
"build": "bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e \"const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))\" && cp -r sql dist/",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
27
|
"start": "bun ./bin/postgres-ai.ts --help",
|
|
28
28
|
"start:node": "node ./dist/bin/postgres-ai.js --help",
|
|
@@ -40,8 +40,6 @@
|
|
|
40
40
|
"@types/bun": "^1.1.14",
|
|
41
41
|
"@types/js-yaml": "^4.0.9",
|
|
42
42
|
"@types/pg": "^8.15.6",
|
|
43
|
-
"ajv": "^8.17.1",
|
|
44
|
-
"ajv-formats": "^3.0.1",
|
|
45
43
|
"typescript": "^5.3.3"
|
|
46
44
|
},
|
|
47
45
|
"publishConfig": {
|
|
@@ -213,7 +213,7 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
|
|
|
213
213
|
} finally {
|
|
214
214
|
await pg.cleanup();
|
|
215
215
|
}
|
|
216
|
-
}
|
|
216
|
+
});
|
|
217
217
|
|
|
218
218
|
test("requires explicit monitoring password in non-interactive mode", async () => {
|
|
219
219
|
pg = await createTempPostgres();
|
|
@@ -237,7 +237,7 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
|
|
|
237
237
|
} finally {
|
|
238
238
|
await pg.cleanup();
|
|
239
239
|
}
|
|
240
|
-
}
|
|
240
|
+
});
|
|
241
241
|
|
|
242
242
|
test("fixes slightly-off permissions idempotently", async () => {
|
|
243
243
|
pg = await createTempPostgres();
|
|
@@ -291,7 +291,7 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
|
|
|
291
291
|
} finally {
|
|
292
292
|
await pg.cleanup();
|
|
293
293
|
}
|
|
294
|
-
}
|
|
294
|
+
});
|
|
295
295
|
|
|
296
296
|
test("reports nicely when lacking permissions", async () => {
|
|
297
297
|
pg = await createTempPostgres();
|
|
@@ -324,7 +324,7 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
|
|
|
324
324
|
} finally {
|
|
325
325
|
await pg.cleanup();
|
|
326
326
|
}
|
|
327
|
-
}
|
|
327
|
+
});
|
|
328
328
|
|
|
329
329
|
test("--verify returns 0 when ok and non-zero when missing", async () => {
|
|
330
330
|
pg = await createTempPostgres();
|
|
@@ -360,7 +360,7 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
|
|
|
360
360
|
} finally {
|
|
361
361
|
await pg.cleanup();
|
|
362
362
|
}
|
|
363
|
-
}
|
|
363
|
+
});
|
|
364
364
|
|
|
365
365
|
test("--reset-password updates the monitoring role login password", async () => {
|
|
366
366
|
pg = await createTempPostgres();
|
|
@@ -392,5 +392,5 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
|
|
|
392
392
|
} finally {
|
|
393
393
|
await pg.cleanup();
|
|
394
394
|
}
|
|
395
|
-
}
|
|
395
|
+
});
|
|
396
396
|
});
|
package/lib/checkup-api.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import * as https from "https";
|
|
2
|
-
import { URL } from "url";
|
|
3
|
-
import { normalizeBaseUrl } from "./util";
|
|
4
|
-
|
|
5
|
-
export class RpcError extends Error {
|
|
6
|
-
rpcName: string;
|
|
7
|
-
statusCode: number;
|
|
8
|
-
payloadText: string;
|
|
9
|
-
payloadJson: any | null;
|
|
10
|
-
|
|
11
|
-
constructor(params: { rpcName: string; statusCode: number; payloadText: string; payloadJson: any | null }) {
|
|
12
|
-
const { rpcName, statusCode, payloadText, payloadJson } = params;
|
|
13
|
-
super(`RPC ${rpcName} failed: HTTP ${statusCode}`);
|
|
14
|
-
this.name = "RpcError";
|
|
15
|
-
this.rpcName = rpcName;
|
|
16
|
-
this.statusCode = statusCode;
|
|
17
|
-
this.payloadText = payloadText;
|
|
18
|
-
this.payloadJson = payloadJson;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function formatRpcErrorForDisplay(err: RpcError): string[] {
|
|
23
|
-
const lines: string[] = [];
|
|
24
|
-
lines.push(`Error: RPC ${err.rpcName} failed: HTTP ${err.statusCode}`);
|
|
25
|
-
|
|
26
|
-
const obj = err.payloadJson && typeof err.payloadJson === "object" ? err.payloadJson : null;
|
|
27
|
-
const details = obj && typeof (obj as any).details === "string" ? (obj as any).details : "";
|
|
28
|
-
const hint = obj && typeof (obj as any).hint === "string" ? (obj as any).hint : "";
|
|
29
|
-
const message = obj && typeof (obj as any).message === "string" ? (obj as any).message : "";
|
|
30
|
-
|
|
31
|
-
if (message) lines.push(`Message: ${message}`);
|
|
32
|
-
if (details) lines.push(`Details: ${details}`);
|
|
33
|
-
if (hint) lines.push(`Hint: ${hint}`);
|
|
34
|
-
|
|
35
|
-
// Fallback to raw payload if we couldn't extract anything useful.
|
|
36
|
-
if (!message && !details && !hint) {
|
|
37
|
-
const t = (err.payloadText || "").trim();
|
|
38
|
-
if (t) lines.push(t);
|
|
39
|
-
}
|
|
40
|
-
return lines;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function unwrapRpcResponse(parsed: unknown): any {
|
|
44
|
-
// Some deployments return a plain object, others return an array of rows,
|
|
45
|
-
// and some wrap OUT params under a "result" key.
|
|
46
|
-
if (Array.isArray(parsed)) {
|
|
47
|
-
if (parsed.length === 1) return unwrapRpcResponse(parsed[0]);
|
|
48
|
-
return parsed;
|
|
49
|
-
}
|
|
50
|
-
if (parsed && typeof parsed === "object") {
|
|
51
|
-
const obj = parsed as any;
|
|
52
|
-
if (obj.result !== undefined) return obj.result;
|
|
53
|
-
}
|
|
54
|
-
return parsed as any;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function postRpc<T>(params: {
|
|
58
|
-
apiKey: string;
|
|
59
|
-
apiBaseUrl: string;
|
|
60
|
-
rpcName: string;
|
|
61
|
-
bodyObj: Record<string, unknown>;
|
|
62
|
-
}): Promise<T> {
|
|
63
|
-
const { apiKey, apiBaseUrl, rpcName, bodyObj } = params;
|
|
64
|
-
if (!apiKey) throw new Error("API key is required");
|
|
65
|
-
const base = normalizeBaseUrl(apiBaseUrl);
|
|
66
|
-
const url = new URL(`${base}/rpc/${rpcName}`);
|
|
67
|
-
const body = JSON.stringify(bodyObj);
|
|
68
|
-
|
|
69
|
-
const headers: Record<string, string> = {
|
|
70
|
-
// The backend RPC functions accept access_token in body, but we also set the header
|
|
71
|
-
// for compatibility with other endpoints and deployments.
|
|
72
|
-
"access-token": apiKey,
|
|
73
|
-
"Prefer": "return=representation",
|
|
74
|
-
"Content-Type": "application/json",
|
|
75
|
-
"Content-Length": Buffer.byteLength(body).toString(),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
return new Promise((resolve, reject) => {
|
|
79
|
-
const req = https.request(
|
|
80
|
-
url,
|
|
81
|
-
{
|
|
82
|
-
method: "POST",
|
|
83
|
-
headers,
|
|
84
|
-
},
|
|
85
|
-
(res) => {
|
|
86
|
-
let data = "";
|
|
87
|
-
res.on("data", (chunk) => (data += chunk));
|
|
88
|
-
res.on("end", () => {
|
|
89
|
-
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
90
|
-
try {
|
|
91
|
-
const parsed = JSON.parse(data);
|
|
92
|
-
resolve(unwrapRpcResponse(parsed) as T);
|
|
93
|
-
} catch {
|
|
94
|
-
reject(new Error(`Failed to parse RPC response: ${data}`));
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
const statusCode = res.statusCode || 0;
|
|
98
|
-
let payloadJson: any | null = null;
|
|
99
|
-
if (data) {
|
|
100
|
-
try {
|
|
101
|
-
payloadJson = JSON.parse(data);
|
|
102
|
-
} catch {
|
|
103
|
-
payloadJson = null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
reject(new RpcError({ rpcName, statusCode, payloadText: data, payloadJson }));
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
);
|
|
111
|
-
req.on("error", (err: Error) => reject(err));
|
|
112
|
-
req.write(body);
|
|
113
|
-
req.end();
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export async function createCheckupReport(params: {
|
|
118
|
-
apiKey: string;
|
|
119
|
-
apiBaseUrl: string;
|
|
120
|
-
project: string;
|
|
121
|
-
status?: string;
|
|
122
|
-
}): Promise<{ reportId: number }> {
|
|
123
|
-
const { apiKey, apiBaseUrl, project, status } = params;
|
|
124
|
-
const bodyObj: Record<string, unknown> = {
|
|
125
|
-
access_token: apiKey,
|
|
126
|
-
project,
|
|
127
|
-
};
|
|
128
|
-
if (status) bodyObj.status = status;
|
|
129
|
-
|
|
130
|
-
const resp = await postRpc<any>({
|
|
131
|
-
apiKey,
|
|
132
|
-
apiBaseUrl,
|
|
133
|
-
rpcName: "checkup_report_create",
|
|
134
|
-
bodyObj,
|
|
135
|
-
});
|
|
136
|
-
const reportId = Number(resp?.report_id);
|
|
137
|
-
if (!Number.isFinite(reportId) || reportId <= 0) {
|
|
138
|
-
throw new Error(`Unexpected checkup_report_create response: ${JSON.stringify(resp)}`);
|
|
139
|
-
}
|
|
140
|
-
return { reportId };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export async function uploadCheckupReportJson(params: {
|
|
144
|
-
apiKey: string;
|
|
145
|
-
apiBaseUrl: string;
|
|
146
|
-
reportId: number;
|
|
147
|
-
filename: string;
|
|
148
|
-
checkId: string;
|
|
149
|
-
jsonText: string;
|
|
150
|
-
}): Promise<{ reportChunkId: number }> {
|
|
151
|
-
const { apiKey, apiBaseUrl, reportId, filename, checkId, jsonText } = params;
|
|
152
|
-
const bodyObj: Record<string, unknown> = {
|
|
153
|
-
access_token: apiKey,
|
|
154
|
-
checkup_report_id: reportId,
|
|
155
|
-
filename,
|
|
156
|
-
check_id: checkId,
|
|
157
|
-
data: jsonText,
|
|
158
|
-
type: "json",
|
|
159
|
-
generate_issue: true,
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const resp = await postRpc<any>({
|
|
163
|
-
apiKey,
|
|
164
|
-
apiBaseUrl,
|
|
165
|
-
rpcName: "checkup_report_file_post",
|
|
166
|
-
bodyObj,
|
|
167
|
-
});
|
|
168
|
-
const chunkId = Number(resp?.report_chunck_id ?? resp?.report_chunk_id);
|
|
169
|
-
if (!Number.isFinite(chunkId) || chunkId <= 0) {
|
|
170
|
-
throw new Error(`Unexpected checkup_report_file_post response: ${JSON.stringify(resp)}`);
|
|
171
|
-
}
|
|
172
|
-
return { reportChunkId: chunkId };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|