postgresai 0.14.0-dev.7 → 0.14.0-dev.71

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 (83) hide show
  1. package/README.md +161 -61
  2. package/bin/postgres-ai.ts +1982 -404
  3. package/bun.lock +258 -0
  4. package/bunfig.toml +20 -0
  5. package/dist/bin/postgres-ai.js +29395 -1576
  6. package/dist/sql/01.role.sql +16 -0
  7. package/dist/sql/02.permissions.sql +37 -0
  8. package/dist/sql/03.optional_rds.sql +6 -0
  9. package/dist/sql/04.optional_self_managed.sql +8 -0
  10. package/dist/sql/05.helpers.sql +439 -0
  11. package/dist/sql/sql/01.role.sql +16 -0
  12. package/dist/sql/sql/02.permissions.sql +37 -0
  13. package/dist/sql/sql/03.optional_rds.sql +6 -0
  14. package/dist/sql/sql/04.optional_self_managed.sql +8 -0
  15. package/dist/sql/sql/05.helpers.sql +439 -0
  16. package/lib/auth-server.ts +124 -106
  17. package/lib/checkup-api.ts +386 -0
  18. package/lib/checkup.ts +1396 -0
  19. package/lib/config.ts +6 -3
  20. package/lib/init.ts +568 -155
  21. package/lib/issues.ts +400 -191
  22. package/lib/mcp-server.ts +213 -90
  23. package/lib/metrics-embedded.ts +79 -0
  24. package/lib/metrics-loader.ts +127 -0
  25. package/lib/supabase.ts +769 -0
  26. package/lib/util.ts +61 -0
  27. package/package.json +20 -10
  28. package/packages/postgres-ai/README.md +26 -0
  29. package/packages/postgres-ai/bin/postgres-ai.js +27 -0
  30. package/packages/postgres-ai/package.json +27 -0
  31. package/scripts/embed-metrics.ts +154 -0
  32. package/sql/01.role.sql +16 -0
  33. package/sql/02.permissions.sql +37 -0
  34. package/sql/03.optional_rds.sql +6 -0
  35. package/sql/04.optional_self_managed.sql +8 -0
  36. package/sql/05.helpers.sql +439 -0
  37. package/test/auth.test.ts +258 -0
  38. package/test/checkup.integration.test.ts +321 -0
  39. package/test/checkup.test.ts +1117 -0
  40. package/test/config-consistency.test.ts +36 -0
  41. package/test/init.integration.test.ts +500 -0
  42. package/test/init.test.ts +682 -0
  43. package/test/issues.cli.test.ts +314 -0
  44. package/test/issues.test.ts +456 -0
  45. package/test/mcp-server.test.ts +988 -0
  46. package/test/schema-validation.test.ts +81 -0
  47. package/test/supabase.test.ts +568 -0
  48. package/test/test-utils.ts +128 -0
  49. package/tsconfig.json +12 -20
  50. package/dist/bin/postgres-ai.d.ts +0 -3
  51. package/dist/bin/postgres-ai.d.ts.map +0 -1
  52. package/dist/bin/postgres-ai.js.map +0 -1
  53. package/dist/lib/auth-server.d.ts +0 -31
  54. package/dist/lib/auth-server.d.ts.map +0 -1
  55. package/dist/lib/auth-server.js +0 -263
  56. package/dist/lib/auth-server.js.map +0 -1
  57. package/dist/lib/config.d.ts +0 -45
  58. package/dist/lib/config.d.ts.map +0 -1
  59. package/dist/lib/config.js +0 -181
  60. package/dist/lib/config.js.map +0 -1
  61. package/dist/lib/init.d.ts +0 -61
  62. package/dist/lib/init.d.ts.map +0 -1
  63. package/dist/lib/init.js +0 -359
  64. package/dist/lib/init.js.map +0 -1
  65. package/dist/lib/issues.d.ts +0 -75
  66. package/dist/lib/issues.d.ts.map +0 -1
  67. package/dist/lib/issues.js +0 -336
  68. package/dist/lib/issues.js.map +0 -1
  69. package/dist/lib/mcp-server.d.ts +0 -9
  70. package/dist/lib/mcp-server.d.ts.map +0 -1
  71. package/dist/lib/mcp-server.js +0 -168
  72. package/dist/lib/mcp-server.js.map +0 -1
  73. package/dist/lib/pkce.d.ts +0 -32
  74. package/dist/lib/pkce.d.ts.map +0 -1
  75. package/dist/lib/pkce.js +0 -101
  76. package/dist/lib/pkce.js.map +0 -1
  77. package/dist/lib/util.d.ts +0 -27
  78. package/dist/lib/util.d.ts.map +0 -1
  79. package/dist/lib/util.js +0 -46
  80. package/dist/lib/util.js.map +0 -1
  81. package/dist/package.json +0 -46
  82. package/test/init.integration.test.cjs +0 -269
  83. package/test/init.test.cjs +0 -69
package/lib/issues.ts CHANGED
@@ -1,6 +1,15 @@
1
- import * as https from "https";
2
- import { URL } from "url";
3
- import { maskSecret, normalizeBaseUrl } from "./util";
1
+ import { formatHttpError, maskSecret, normalizeBaseUrl } from "./util";
2
+
3
+ /**
4
+ * Issue status constants.
5
+ * Used in updateIssue to change issue state.
6
+ */
7
+ export const IssueStatus = {
8
+ /** Issue is open and active */
9
+ OPEN: 0,
10
+ /** Issue is closed/resolved */
11
+ CLOSED: 1,
12
+ } as const;
4
13
 
5
14
  export interface IssueActionItem {
6
15
  id: string;
@@ -71,63 +80,38 @@ export async function fetchIssues(params: FetchIssuesParams): Promise<IssueListI
71
80
  "access-token": apiKey,
72
81
  "Prefer": "return=representation",
73
82
  "Content-Type": "application/json",
83
+ "Connection": "close",
74
84
  };
75
85
 
76
86
  if (debug) {
77
87
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
78
- // eslint-disable-next-line no-console
79
88
  console.log(`Debug: Resolved API base URL: ${base}`);
80
- // eslint-disable-next-line no-console
81
89
  console.log(`Debug: GET URL: ${url.toString()}`);
82
- // eslint-disable-next-line no-console
83
90
  console.log(`Debug: Auth scheme: access-token`);
84
- // eslint-disable-next-line no-console
85
91
  console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
86
92
  }
87
93
 
88
- return new Promise((resolve, reject) => {
89
- const req = https.request(
90
- url,
91
- {
92
- method: "GET",
93
- headers,
94
- },
95
- (res) => {
96
- let data = "";
97
- res.on("data", (chunk) => (data += chunk));
98
- res.on("end", () => {
99
- if (debug) {
100
- // eslint-disable-next-line no-console
101
- console.log(`Debug: Response status: ${res.statusCode}`);
102
- // eslint-disable-next-line no-console
103
- console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
104
- }
105
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
106
- try {
107
- const parsed = JSON.parse(data) as IssueListItem[];
108
- resolve(parsed);
109
- } catch {
110
- reject(new Error(`Failed to parse issues response: ${data}`));
111
- }
112
- } else {
113
- let errMsg = `Failed to fetch issues: HTTP ${res.statusCode}`;
114
- if (data) {
115
- try {
116
- const errObj = JSON.parse(data);
117
- errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
118
- } catch {
119
- errMsg += `\n${data}`;
120
- }
121
- }
122
- reject(new Error(errMsg));
123
- }
124
- });
125
- }
126
- );
127
-
128
- req.on("error", (err: Error) => reject(err));
129
- req.end();
94
+ const response = await fetch(url.toString(), {
95
+ method: "GET",
96
+ headers,
130
97
  });
98
+
99
+ if (debug) {
100
+ console.log(`Debug: Response status: ${response.status}`);
101
+ console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
102
+ }
103
+
104
+ const data = await response.text();
105
+
106
+ if (response.ok) {
107
+ try {
108
+ return JSON.parse(data) as IssueListItem[];
109
+ } catch {
110
+ throw new Error(`Failed to parse issues response: ${data}`);
111
+ }
112
+ } else {
113
+ throw new Error(formatHttpError("Failed to fetch issues", response.status, data));
114
+ }
131
115
  }
132
116
 
133
117
 
@@ -154,63 +138,38 @@ export async function fetchIssueComments(params: FetchIssueCommentsParams): Prom
154
138
  "access-token": apiKey,
155
139
  "Prefer": "return=representation",
156
140
  "Content-Type": "application/json",
141
+ "Connection": "close",
157
142
  };
158
143
 
159
144
  if (debug) {
160
145
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
161
- // eslint-disable-next-line no-console
162
146
  console.log(`Debug: Resolved API base URL: ${base}`);
163
- // eslint-disable-next-line no-console
164
147
  console.log(`Debug: GET URL: ${url.toString()}`);
165
- // eslint-disable-next-line no-console
166
148
  console.log(`Debug: Auth scheme: access-token`);
167
- // eslint-disable-next-line no-console
168
149
  console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
169
150
  }
170
151
 
171
- return new Promise((resolve, reject) => {
172
- const req = https.request(
173
- url,
174
- {
175
- method: "GET",
176
- headers,
177
- },
178
- (res) => {
179
- let data = "";
180
- res.on("data", (chunk) => (data += chunk));
181
- res.on("end", () => {
182
- if (debug) {
183
- // eslint-disable-next-line no-console
184
- console.log(`Debug: Response status: ${res.statusCode}`);
185
- // eslint-disable-next-line no-console
186
- console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
187
- }
188
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
189
- try {
190
- const parsed = JSON.parse(data) as IssueComment[];
191
- resolve(parsed);
192
- } catch {
193
- reject(new Error(`Failed to parse issue comments response: ${data}`));
194
- }
195
- } else {
196
- let errMsg = `Failed to fetch issue comments: HTTP ${res.statusCode}`;
197
- if (data) {
198
- try {
199
- const errObj = JSON.parse(data);
200
- errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
201
- } catch {
202
- errMsg += `\n${data}`;
203
- }
204
- }
205
- reject(new Error(errMsg));
206
- }
207
- });
208
- }
209
- );
210
-
211
- req.on("error", (err: Error) => reject(err));
212
- req.end();
152
+ const response = await fetch(url.toString(), {
153
+ method: "GET",
154
+ headers,
213
155
  });
156
+
157
+ if (debug) {
158
+ console.log(`Debug: Response status: ${response.status}`);
159
+ console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
160
+ }
161
+
162
+ const data = await response.text();
163
+
164
+ if (response.ok) {
165
+ try {
166
+ return JSON.parse(data) as IssueComment[];
167
+ } catch {
168
+ throw new Error(`Failed to parse issue comments response: ${data}`);
169
+ }
170
+ } else {
171
+ throw new Error(formatHttpError("Failed to fetch issue comments", response.status, data));
172
+ }
214
173
  }
215
174
 
216
175
  export interface FetchIssueParams {
@@ -239,67 +198,149 @@ export async function fetchIssue(params: FetchIssueParams): Promise<IssueDetail
239
198
  "access-token": apiKey,
240
199
  "Prefer": "return=representation",
241
200
  "Content-Type": "application/json",
201
+ "Connection": "close",
242
202
  };
243
203
 
244
204
  if (debug) {
245
205
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
246
- // eslint-disable-next-line no-console
247
206
  console.log(`Debug: Resolved API base URL: ${base}`);
248
- // eslint-disable-next-line no-console
249
207
  console.log(`Debug: GET URL: ${url.toString()}`);
250
- // eslint-disable-next-line no-console
251
208
  console.log(`Debug: Auth scheme: access-token`);
252
- // eslint-disable-next-line no-console
253
209
  console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
254
210
  }
255
211
 
256
- return new Promise((resolve, reject) => {
257
- const req = https.request(
258
- url,
259
- {
260
- method: "GET",
261
- headers,
262
- },
263
- (res) => {
264
- let data = "";
265
- res.on("data", (chunk) => (data += chunk));
266
- res.on("end", () => {
267
- if (debug) {
268
- // eslint-disable-next-line no-console
269
- console.log(`Debug: Response status: ${res.statusCode}`);
270
- // eslint-disable-next-line no-console
271
- console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
272
- }
273
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
274
- try {
275
- const parsed = JSON.parse(data);
276
- if (Array.isArray(parsed)) {
277
- resolve((parsed[0] as IssueDetail) ?? null);
278
- } else {
279
- resolve(parsed as IssueDetail);
280
- }
281
- } catch {
282
- reject(new Error(`Failed to parse issue response: ${data}`));
283
- }
284
- } else {
285
- let errMsg = `Failed to fetch issue: HTTP ${res.statusCode}`;
286
- if (data) {
287
- try {
288
- const errObj = JSON.parse(data);
289
- errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
290
- } catch {
291
- errMsg += `\n${data}`;
292
- }
293
- }
294
- reject(new Error(errMsg));
295
- }
296
- });
212
+ const response = await fetch(url.toString(), {
213
+ method: "GET",
214
+ headers,
215
+ });
216
+
217
+ if (debug) {
218
+ console.log(`Debug: Response status: ${response.status}`);
219
+ console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
220
+ }
221
+
222
+ const data = await response.text();
223
+
224
+ if (response.ok) {
225
+ try {
226
+ const parsed = JSON.parse(data);
227
+ if (Array.isArray(parsed)) {
228
+ return (parsed[0] as IssueDetail) ?? null;
229
+ } else {
230
+ return parsed as IssueDetail;
297
231
  }
298
- );
232
+ } catch {
233
+ throw new Error(`Failed to parse issue response: ${data}`);
234
+ }
235
+ } else {
236
+ throw new Error(formatHttpError("Failed to fetch issue", response.status, data));
237
+ }
238
+ }
239
+
240
+ export interface CreateIssueParams {
241
+ apiKey: string;
242
+ apiBaseUrl: string;
243
+ title: string;
244
+ orgId: number;
245
+ description?: string;
246
+ projectId?: number;
247
+ labels?: string[];
248
+ debug?: boolean;
249
+ }
250
+
251
+ export interface CreatedIssue {
252
+ id: string;
253
+ title: string;
254
+ description: string | null;
255
+ created_at: string;
256
+ status: number;
257
+ project_id: number | null;
258
+ labels: string[] | null;
259
+ }
260
+
261
+ /**
262
+ * Create a new issue in the PostgresAI platform.
263
+ *
264
+ * @param params - The parameters for creating an issue
265
+ * @param params.apiKey - API key for authentication
266
+ * @param params.apiBaseUrl - Base URL for the API
267
+ * @param params.title - Issue title (required)
268
+ * @param params.orgId - Organization ID (required)
269
+ * @param params.description - Optional issue description
270
+ * @param params.projectId - Optional project ID to associate with
271
+ * @param params.labels - Optional array of label strings
272
+ * @param params.debug - Enable debug logging
273
+ * @returns The created issue object
274
+ * @throws Error if API key, title, or orgId is missing, or if the API call fails
275
+ */
276
+ export async function createIssue(params: CreateIssueParams): Promise<CreatedIssue> {
277
+ const { apiKey, apiBaseUrl, title, orgId, description, projectId, labels, debug } = params;
278
+ if (!apiKey) {
279
+ throw new Error("API key is required");
280
+ }
281
+ if (!title) {
282
+ throw new Error("title is required");
283
+ }
284
+ if (typeof orgId !== "number") {
285
+ throw new Error("orgId is required");
286
+ }
287
+
288
+ const base = normalizeBaseUrl(apiBaseUrl);
289
+ const url = new URL(`${base}/rpc/issue_create`);
299
290
 
300
- req.on("error", (err: Error) => reject(err));
301
- req.end();
291
+ const bodyObj: Record<string, unknown> = {
292
+ title: title,
293
+ org_id: orgId,
294
+ };
295
+ if (description !== undefined) {
296
+ bodyObj.description = description;
297
+ }
298
+ if (projectId !== undefined) {
299
+ bodyObj.project_id = projectId;
300
+ }
301
+ if (labels && labels.length > 0) {
302
+ bodyObj.labels = labels;
303
+ }
304
+ const body = JSON.stringify(bodyObj);
305
+
306
+ const headers: Record<string, string> = {
307
+ "access-token": apiKey,
308
+ "Prefer": "return=representation",
309
+ "Content-Type": "application/json",
310
+ "Connection": "close",
311
+ };
312
+
313
+ if (debug) {
314
+ const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
315
+ console.log(`Debug: Resolved API base URL: ${base}`);
316
+ console.log(`Debug: POST URL: ${url.toString()}`);
317
+ console.log(`Debug: Auth scheme: access-token`);
318
+ console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
319
+ console.log(`Debug: Request body: ${body}`);
320
+ }
321
+
322
+ const response = await fetch(url.toString(), {
323
+ method: "POST",
324
+ headers,
325
+ body,
302
326
  });
327
+
328
+ if (debug) {
329
+ console.log(`Debug: Response status: ${response.status}`);
330
+ console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
331
+ }
332
+
333
+ const data = await response.text();
334
+
335
+ if (response.ok) {
336
+ try {
337
+ return JSON.parse(data) as CreatedIssue;
338
+ } catch {
339
+ throw new Error(`Failed to parse create issue response: ${data}`);
340
+ }
341
+ } else {
342
+ throw new Error(formatHttpError("Failed to create issue", response.status, data));
343
+ }
303
344
  }
304
345
 
305
346
  export interface CreateIssueCommentParams {
@@ -339,67 +380,235 @@ export async function createIssueComment(params: CreateIssueCommentParams): Prom
339
380
  "access-token": apiKey,
340
381
  "Prefer": "return=representation",
341
382
  "Content-Type": "application/json",
342
- "Content-Length": Buffer.byteLength(body).toString(),
383
+ "Connection": "close",
343
384
  };
344
385
 
345
386
  if (debug) {
346
387
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
347
- // eslint-disable-next-line no-console
348
388
  console.log(`Debug: Resolved API base URL: ${base}`);
349
- // eslint-disable-next-line no-console
350
389
  console.log(`Debug: POST URL: ${url.toString()}`);
351
- // eslint-disable-next-line no-console
352
390
  console.log(`Debug: Auth scheme: access-token`);
353
- // eslint-disable-next-line no-console
354
391
  console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
355
- // eslint-disable-next-line no-console
356
392
  console.log(`Debug: Request body: ${body}`);
357
393
  }
358
394
 
359
- return new Promise((resolve, reject) => {
360
- const req = https.request(
361
- url,
362
- {
363
- method: "POST",
364
- headers,
365
- },
366
- (res) => {
367
- let data = "";
368
- res.on("data", (chunk) => (data += chunk));
369
- res.on("end", () => {
370
- if (debug) {
371
- // eslint-disable-next-line no-console
372
- console.log(`Debug: Response status: ${res.statusCode}`);
373
- // eslint-disable-next-line no-console
374
- console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
375
- }
376
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
377
- try {
378
- const parsed = JSON.parse(data) as IssueComment;
379
- resolve(parsed);
380
- } catch {
381
- reject(new Error(`Failed to parse create comment response: ${data}`));
382
- }
383
- } else {
384
- let errMsg = `Failed to create issue comment: HTTP ${res.statusCode}`;
385
- if (data) {
386
- try {
387
- const errObj = JSON.parse(data);
388
- errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
389
- } catch {
390
- errMsg += `\n${data}`;
391
- }
392
- }
393
- reject(new Error(errMsg));
394
- }
395
- });
396
- }
397
- );
395
+ const response = await fetch(url.toString(), {
396
+ method: "POST",
397
+ headers,
398
+ body,
399
+ });
400
+
401
+ if (debug) {
402
+ console.log(`Debug: Response status: ${response.status}`);
403
+ console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
404
+ }
405
+
406
+ const data = await response.text();
398
407
 
399
- req.on("error", (err: Error) => reject(err));
400
- req.write(body);
401
- req.end();
408
+ if (response.ok) {
409
+ try {
410
+ return JSON.parse(data) as IssueComment;
411
+ } catch {
412
+ throw new Error(`Failed to parse create comment response: ${data}`);
413
+ }
414
+ } else {
415
+ throw new Error(formatHttpError("Failed to create issue comment", response.status, data));
416
+ }
417
+ }
418
+
419
+ export interface UpdateIssueParams {
420
+ apiKey: string;
421
+ apiBaseUrl: string;
422
+ issueId: string;
423
+ title?: string;
424
+ description?: string;
425
+ status?: number;
426
+ labels?: string[];
427
+ debug?: boolean;
428
+ }
429
+
430
+ export interface UpdatedIssue {
431
+ id: string;
432
+ title: string;
433
+ description: string | null;
434
+ status: number;
435
+ updated_at: string;
436
+ labels: string[] | null;
437
+ }
438
+
439
+ /**
440
+ * Update an existing issue in the PostgresAI platform.
441
+ *
442
+ * @param params - The parameters for updating an issue
443
+ * @param params.apiKey - API key for authentication
444
+ * @param params.apiBaseUrl - Base URL for the API
445
+ * @param params.issueId - ID of the issue to update (required)
446
+ * @param params.title - New title (optional)
447
+ * @param params.description - New description (optional)
448
+ * @param params.status - New status: 0 = open, 1 = closed (optional)
449
+ * @param params.labels - New labels array (optional, replaces existing)
450
+ * @param params.debug - Enable debug logging
451
+ * @returns The updated issue object
452
+ * @throws Error if API key or issueId is missing, if no fields to update are provided, or if the API call fails
453
+ */
454
+ export async function updateIssue(params: UpdateIssueParams): Promise<UpdatedIssue> {
455
+ const { apiKey, apiBaseUrl, issueId, title, description, status, labels, debug } = params;
456
+ if (!apiKey) {
457
+ throw new Error("API key is required");
458
+ }
459
+ if (!issueId) {
460
+ throw new Error("issueId is required");
461
+ }
462
+ if (title === undefined && description === undefined && status === undefined && labels === undefined) {
463
+ throw new Error("At least one field to update is required (title, description, status, or labels)");
464
+ }
465
+
466
+ const base = normalizeBaseUrl(apiBaseUrl);
467
+ const url = new URL(`${base}/rpc/issue_update`);
468
+
469
+ // Prod RPC expects p_* argument names (see OpenAPI at /api/general/).
470
+ const bodyObj: Record<string, unknown> = {
471
+ p_id: issueId,
472
+ };
473
+ if (title !== undefined) {
474
+ bodyObj.p_title = title;
475
+ }
476
+ if (description !== undefined) {
477
+ bodyObj.p_description = description;
478
+ }
479
+ if (status !== undefined) {
480
+ bodyObj.p_status = status;
481
+ }
482
+ if (labels !== undefined) {
483
+ bodyObj.p_labels = labels;
484
+ }
485
+ const body = JSON.stringify(bodyObj);
486
+
487
+ const headers: Record<string, string> = {
488
+ "access-token": apiKey,
489
+ "Prefer": "return=representation",
490
+ "Content-Type": "application/json",
491
+ "Connection": "close",
492
+ };
493
+
494
+ if (debug) {
495
+ const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
496
+ console.log(`Debug: Resolved API base URL: ${base}`);
497
+ console.log(`Debug: POST URL: ${url.toString()}`);
498
+ console.log(`Debug: Auth scheme: access-token`);
499
+ console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
500
+ console.log(`Debug: Request body: ${body}`);
501
+ }
502
+
503
+ const response = await fetch(url.toString(), {
504
+ method: "POST",
505
+ headers,
506
+ body,
402
507
  });
508
+
509
+ if (debug) {
510
+ console.log(`Debug: Response status: ${response.status}`);
511
+ console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
512
+ }
513
+
514
+ const data = await response.text();
515
+
516
+ if (response.ok) {
517
+ try {
518
+ return JSON.parse(data) as UpdatedIssue;
519
+ } catch {
520
+ throw new Error(`Failed to parse update issue response: ${data}`);
521
+ }
522
+ } else {
523
+ throw new Error(formatHttpError("Failed to update issue", response.status, data));
524
+ }
525
+ }
526
+
527
+ export interface UpdateIssueCommentParams {
528
+ apiKey: string;
529
+ apiBaseUrl: string;
530
+ commentId: string;
531
+ content: string;
532
+ debug?: boolean;
533
+ }
534
+
535
+ export interface UpdatedIssueComment {
536
+ id: string;
537
+ issue_id: string;
538
+ content: string;
539
+ updated_at: string;
403
540
  }
404
541
 
542
+ /**
543
+ * Update an existing issue comment in the PostgresAI platform.
544
+ *
545
+ * @param params - The parameters for updating a comment
546
+ * @param params.apiKey - API key for authentication
547
+ * @param params.apiBaseUrl - Base URL for the API
548
+ * @param params.commentId - ID of the comment to update (required)
549
+ * @param params.content - New comment content (required)
550
+ * @param params.debug - Enable debug logging
551
+ * @returns The updated comment object
552
+ * @throws Error if API key, commentId, or content is missing, or if the API call fails
553
+ */
554
+ export async function updateIssueComment(params: UpdateIssueCommentParams): Promise<UpdatedIssueComment> {
555
+ const { apiKey, apiBaseUrl, commentId, content, debug } = params;
556
+ if (!apiKey) {
557
+ throw new Error("API key is required");
558
+ }
559
+ if (!commentId) {
560
+ throw new Error("commentId is required");
561
+ }
562
+ if (!content) {
563
+ throw new Error("content is required");
564
+ }
565
+
566
+ const base = normalizeBaseUrl(apiBaseUrl);
567
+ const url = new URL(`${base}/rpc/issue_comment_update`);
568
+
569
+ const bodyObj: Record<string, unknown> = {
570
+ // Prod RPC expects p_* argument names (see OpenAPI at /api/general/).
571
+ p_id: commentId,
572
+ p_content: content,
573
+ };
574
+ const body = JSON.stringify(bodyObj);
575
+
576
+ const headers: Record<string, string> = {
577
+ "access-token": apiKey,
578
+ "Prefer": "return=representation",
579
+ "Content-Type": "application/json",
580
+ "Connection": "close",
581
+ };
405
582
 
583
+ if (debug) {
584
+ const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
585
+ console.log(`Debug: Resolved API base URL: ${base}`);
586
+ console.log(`Debug: POST URL: ${url.toString()}`);
587
+ console.log(`Debug: Auth scheme: access-token`);
588
+ console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
589
+ console.log(`Debug: Request body: ${body}`);
590
+ }
591
+
592
+ const response = await fetch(url.toString(), {
593
+ method: "POST",
594
+ headers,
595
+ body,
596
+ });
597
+
598
+ if (debug) {
599
+ console.log(`Debug: Response status: ${response.status}`);
600
+ console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
601
+ }
602
+
603
+ const data = await response.text();
604
+
605
+ if (response.ok) {
606
+ try {
607
+ return JSON.parse(data) as UpdatedIssueComment;
608
+ } catch {
609
+ throw new Error(`Failed to parse update comment response: ${data}`);
610
+ }
611
+ } else {
612
+ throw new Error(formatHttpError("Failed to update issue comment", response.status, data));
613
+ }
614
+ }