postgresai 0.14.0-dev.8 → 0.14.0-dev.80

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 (96) hide show
  1. package/README.md +161 -61
  2. package/bin/postgres-ai.ts +2596 -428
  3. package/bun.lock +258 -0
  4. package/bunfig.toml +20 -0
  5. package/dist/bin/postgres-ai.js +31218 -1575
  6. package/dist/sql/01.role.sql +16 -0
  7. package/dist/sql/02.extensions.sql +8 -0
  8. package/dist/sql/03.permissions.sql +38 -0
  9. package/dist/sql/04.optional_rds.sql +6 -0
  10. package/dist/sql/05.optional_self_managed.sql +8 -0
  11. package/dist/sql/06.helpers.sql +439 -0
  12. package/dist/sql/sql/01.role.sql +16 -0
  13. package/dist/sql/sql/02.extensions.sql +8 -0
  14. package/dist/sql/sql/03.permissions.sql +38 -0
  15. package/dist/sql/sql/04.optional_rds.sql +6 -0
  16. package/dist/sql/sql/05.optional_self_managed.sql +8 -0
  17. package/dist/sql/sql/06.helpers.sql +439 -0
  18. package/dist/sql/sql/uninit/01.helpers.sql +5 -0
  19. package/dist/sql/sql/uninit/02.permissions.sql +30 -0
  20. package/dist/sql/sql/uninit/03.role.sql +27 -0
  21. package/dist/sql/uninit/01.helpers.sql +5 -0
  22. package/dist/sql/uninit/02.permissions.sql +30 -0
  23. package/dist/sql/uninit/03.role.sql +27 -0
  24. package/lib/auth-server.ts +124 -106
  25. package/lib/checkup-api.ts +386 -0
  26. package/lib/checkup-dictionary.ts +113 -0
  27. package/lib/checkup.ts +1435 -0
  28. package/lib/config.ts +6 -3
  29. package/lib/init.ts +655 -189
  30. package/lib/issues.ts +848 -193
  31. package/lib/mcp-server.ts +391 -91
  32. package/lib/metrics-loader.ts +127 -0
  33. package/lib/supabase.ts +824 -0
  34. package/lib/util.ts +61 -0
  35. package/package.json +22 -10
  36. package/packages/postgres-ai/README.md +26 -0
  37. package/packages/postgres-ai/bin/postgres-ai.js +27 -0
  38. package/packages/postgres-ai/package.json +27 -0
  39. package/scripts/embed-checkup-dictionary.ts +106 -0
  40. package/scripts/embed-metrics.ts +154 -0
  41. package/sql/01.role.sql +16 -0
  42. package/sql/02.extensions.sql +8 -0
  43. package/sql/03.permissions.sql +38 -0
  44. package/sql/04.optional_rds.sql +6 -0
  45. package/sql/05.optional_self_managed.sql +8 -0
  46. package/sql/06.helpers.sql +439 -0
  47. package/sql/uninit/01.helpers.sql +5 -0
  48. package/sql/uninit/02.permissions.sql +30 -0
  49. package/sql/uninit/03.role.sql +27 -0
  50. package/test/auth.test.ts +258 -0
  51. package/test/checkup.integration.test.ts +321 -0
  52. package/test/checkup.test.ts +1116 -0
  53. package/test/config-consistency.test.ts +36 -0
  54. package/test/init.integration.test.ts +508 -0
  55. package/test/init.test.ts +916 -0
  56. package/test/issues.cli.test.ts +538 -0
  57. package/test/issues.test.ts +456 -0
  58. package/test/mcp-server.test.ts +1527 -0
  59. package/test/schema-validation.test.ts +81 -0
  60. package/test/supabase.test.ts +568 -0
  61. package/test/test-utils.ts +128 -0
  62. package/tsconfig.json +12 -20
  63. package/dist/bin/postgres-ai.d.ts +0 -3
  64. package/dist/bin/postgres-ai.d.ts.map +0 -1
  65. package/dist/bin/postgres-ai.js.map +0 -1
  66. package/dist/lib/auth-server.d.ts +0 -31
  67. package/dist/lib/auth-server.d.ts.map +0 -1
  68. package/dist/lib/auth-server.js +0 -263
  69. package/dist/lib/auth-server.js.map +0 -1
  70. package/dist/lib/config.d.ts +0 -45
  71. package/dist/lib/config.d.ts.map +0 -1
  72. package/dist/lib/config.js +0 -181
  73. package/dist/lib/config.js.map +0 -1
  74. package/dist/lib/init.d.ts +0 -64
  75. package/dist/lib/init.d.ts.map +0 -1
  76. package/dist/lib/init.js +0 -399
  77. package/dist/lib/init.js.map +0 -1
  78. package/dist/lib/issues.d.ts +0 -75
  79. package/dist/lib/issues.d.ts.map +0 -1
  80. package/dist/lib/issues.js +0 -336
  81. package/dist/lib/issues.js.map +0 -1
  82. package/dist/lib/mcp-server.d.ts +0 -9
  83. package/dist/lib/mcp-server.d.ts.map +0 -1
  84. package/dist/lib/mcp-server.js +0 -168
  85. package/dist/lib/mcp-server.js.map +0 -1
  86. package/dist/lib/pkce.d.ts +0 -32
  87. package/dist/lib/pkce.d.ts.map +0 -1
  88. package/dist/lib/pkce.js +0 -101
  89. package/dist/lib/pkce.js.map +0 -1
  90. package/dist/lib/util.d.ts +0 -27
  91. package/dist/lib/util.d.ts.map +0 -1
  92. package/dist/lib/util.js +0 -46
  93. package/dist/lib/util.js.map +0 -1
  94. package/dist/package.json +0 -46
  95. package/test/init.integration.test.cjs +0 -269
  96. package/test/init.test.cjs +0 -76
@@ -1,5 +1,4 @@
1
1
  import * as http from "http";
2
- import { URL } from "url";
3
2
 
4
3
  /**
5
4
  * OAuth callback result
@@ -13,8 +12,9 @@ export interface CallbackResult {
13
12
  * Callback server structure
14
13
  */
15
14
  export interface CallbackServer {
16
- server: http.Server;
15
+ server: { stop: () => void };
17
16
  promise: Promise<CallbackResult>;
17
+ ready: Promise<number>; // Resolves with actual port when server is listening
18
18
  getPort: () => number;
19
19
  }
20
20
 
@@ -34,11 +34,21 @@ function escapeHtml(str: string | null): string {
34
34
  }
35
35
 
36
36
  /**
37
- * Create and start callback server, returning server object and promise
37
+ * Create and start callback server using Node.js http module
38
+ *
38
39
  * @param port - Port to listen on (0 for random available port)
39
40
  * @param expectedState - Expected state parameter for CSRF protection
40
41
  * @param timeoutMs - Timeout in milliseconds
41
- * @returns Server object with promise and getPort function
42
+ * @returns Server object with promise, ready promise, and getPort function
43
+ *
44
+ * @remarks
45
+ * The `ready` promise resolves with the actual port once the server is listening.
46
+ * Callers should await `ready` before using `getPort()` when using port 0.
47
+ *
48
+ * The server stops asynchronously ~100ms after the callback resolves/rejects.
49
+ * This delay ensures the HTTP response is fully sent before closing the connection.
50
+ * Callers should not attempt to reuse the same port immediately after the promise
51
+ * resolves - wait at least 200ms or use a different port.
42
52
  */
43
53
  export function createCallbackServer(
44
54
  port: number = 0,
@@ -46,54 +56,81 @@ export function createCallbackServer(
46
56
  timeoutMs: number = 300000
47
57
  ): CallbackServer {
48
58
  let resolved = false;
49
- let server: http.Server | null = null;
50
59
  let actualPort = port;
51
60
  let resolveCallback: (value: CallbackResult) => void;
52
61
  let rejectCallback: (reason: Error) => void;
53
-
62
+ let resolveReady: (port: number) => void;
63
+ let rejectReady: (reason: Error) => void;
64
+ let serverInstance: http.Server | null = null;
65
+
54
66
  const promise = new Promise<CallbackResult>((resolve, reject) => {
55
67
  resolveCallback = resolve;
56
68
  rejectCallback = reject;
57
69
  });
58
-
70
+
71
+ const ready = new Promise<number>((resolve, reject) => {
72
+ resolveReady = resolve;
73
+ rejectReady = reject;
74
+ });
75
+
76
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
77
+
78
+ const stopServer = () => {
79
+ // Clear timeout to prevent it firing after manual stop
80
+ if (timeoutId) {
81
+ clearTimeout(timeoutId);
82
+ timeoutId = null;
83
+ }
84
+ if (serverInstance) {
85
+ serverInstance.close();
86
+ serverInstance = null;
87
+ }
88
+ };
89
+
59
90
  // Timeout handler
60
- const timeout = setTimeout(() => {
91
+ timeoutId = setTimeout(() => {
61
92
  if (!resolved) {
62
93
  resolved = true;
63
- if (server) {
64
- server.close();
65
- }
94
+ timeoutId = null; // Already fired, clear reference
95
+ stopServer();
66
96
  rejectCallback(new Error("Authentication timeout. Please try again."));
67
97
  }
68
98
  }, timeoutMs);
69
-
70
- // Request handler
71
- const requestHandler = (req: http.IncomingMessage, res: http.ServerResponse): void => {
99
+
100
+ serverInstance = http.createServer((req, res) => {
72
101
  if (resolved) {
102
+ res.writeHead(200, { "Content-Type": "text/plain" });
103
+ res.end("Already handled");
73
104
  return;
74
105
  }
75
106
 
107
+ const url = new URL(req.url || "/", `http://127.0.0.1:${actualPort}`);
108
+
76
109
  // Only handle /callback path
77
- if (!req.url || !req.url.startsWith("/callback")) {
110
+ if (!url.pathname.startsWith("/callback")) {
78
111
  res.writeHead(404, { "Content-Type": "text/plain" });
79
112
  res.end("Not Found");
80
113
  return;
81
114
  }
82
115
 
83
- try {
84
- const url = new URL(req.url, `http://localhost:${actualPort}`);
85
- const code = url.searchParams.get("code");
86
- const state = url.searchParams.get("state");
87
- const error = url.searchParams.get("error");
88
- const errorDescription = url.searchParams.get("error_description");
116
+ const code = url.searchParams.get("code");
117
+ const state = url.searchParams.get("state");
118
+ const error = url.searchParams.get("error");
119
+ const errorDescription = url.searchParams.get("error_description");
120
+
121
+ // Handle OAuth error
122
+ if (error) {
123
+ resolved = true;
124
+ if (timeoutId) {
125
+ clearTimeout(timeoutId);
126
+ timeoutId = null;
127
+ }
89
128
 
90
- // Handle OAuth error
91
- if (error) {
92
- resolved = true;
93
- clearTimeout(timeout);
94
-
95
- res.writeHead(400, { "Content-Type": "text/html" });
96
- res.end(`
129
+ setTimeout(() => stopServer(), 100);
130
+ rejectCallback(new Error(`OAuth error: ${error}${errorDescription ? ` - ${errorDescription}` : ""}`));
131
+
132
+ res.writeHead(400, { "Content-Type": "text/html" });
133
+ res.end(`
97
134
  <!DOCTYPE html>
98
135
  <html>
99
136
  <head>
@@ -114,19 +151,14 @@ export function createCallbackServer(
114
151
  </div>
115
152
  </body>
116
153
  </html>
117
- `);
118
-
119
- if (server) {
120
- server.close();
121
- }
122
- rejectCallback(new Error(`OAuth error: ${error}${errorDescription ? ` - ${errorDescription}` : ""}`));
123
- return;
124
- }
154
+ `);
155
+ return;
156
+ }
125
157
 
126
- // Validate required parameters
127
- if (!code || !state) {
128
- res.writeHead(400, { "Content-Type": "text/html" });
129
- res.end(`
158
+ // Validate required parameters
159
+ if (!code || !state) {
160
+ res.writeHead(400, { "Content-Type": "text/html" });
161
+ res.end(`
130
162
  <!DOCTYPE html>
131
163
  <html>
132
164
  <head>
@@ -145,17 +177,23 @@ export function createCallbackServer(
145
177
  </div>
146
178
  </body>
147
179
  </html>
148
- `);
149
- return;
180
+ `);
181
+ return;
182
+ }
183
+
184
+ // Validate state (CSRF protection)
185
+ if (expectedState && state !== expectedState) {
186
+ resolved = true;
187
+ if (timeoutId) {
188
+ clearTimeout(timeoutId);
189
+ timeoutId = null;
150
190
  }
151
191
 
152
- // Validate state (CSRF protection)
153
- if (expectedState && state !== expectedState) {
154
- resolved = true;
155
- clearTimeout(timeout);
156
-
157
- res.writeHead(400, { "Content-Type": "text/html" });
158
- res.end(`
192
+ setTimeout(() => stopServer(), 100);
193
+ rejectCallback(new Error("State mismatch (possible CSRF attack)"));
194
+
195
+ res.writeHead(400, { "Content-Type": "text/html" });
196
+ res.end(`
159
197
  <!DOCTYPE html>
160
198
  <html>
161
199
  <head>
@@ -174,21 +212,24 @@ export function createCallbackServer(
174
212
  </div>
175
213
  </body>
176
214
  </html>
177
- `);
178
-
179
- if (server) {
180
- server.close();
181
- }
182
- rejectCallback(new Error("State mismatch (possible CSRF attack)"));
183
- return;
184
- }
215
+ `);
216
+ return;
217
+ }
185
218
 
186
- // Success!
187
- resolved = true;
188
- clearTimeout(timeout);
189
-
190
- res.writeHead(200, { "Content-Type": "text/html" });
191
- res.end(`
219
+ // Success!
220
+ resolved = true;
221
+ if (timeoutId) {
222
+ clearTimeout(timeoutId);
223
+ timeoutId = null;
224
+ }
225
+
226
+ // Resolve first, then stop server asynchronously after response is sent.
227
+ // The 100ms delay ensures the HTTP response is fully written before closing.
228
+ resolveCallback({ code, state });
229
+ setTimeout(() => stopServer(), 100);
230
+
231
+ res.writeHead(200, { "Content-Type": "text/html" });
232
+ res.end(`
192
233
  <!DOCTYPE html>
193
234
  <html>
194
235
  <head>
@@ -207,61 +248,38 @@ export function createCallbackServer(
207
248
  </div>
208
249
  </body>
209
250
  </html>
210
- `);
211
-
212
- if (server) {
213
- server.close();
214
- }
215
- resolveCallback({ code, state });
216
- } catch (err) {
217
- if (!resolved) {
218
- resolved = true;
219
- clearTimeout(timeout);
220
- res.writeHead(500, { "Content-Type": "text/plain" });
221
- res.end("Internal Server Error");
222
- if (server) {
223
- server.close();
224
- }
225
- rejectCallback(err instanceof Error ? err : new Error(String(err)));
226
- }
227
- }
228
- };
251
+ `);
252
+ });
229
253
 
230
- // Create server
231
- server = http.createServer(requestHandler);
232
-
233
- server.on("error", (err: Error) => {
254
+ // Handle server errors (e.g., EADDRINUSE)
255
+ serverInstance.on("error", (err: NodeJS.ErrnoException) => {
256
+ if (timeoutId) {
257
+ clearTimeout(timeoutId);
258
+ timeoutId = null;
259
+ }
260
+ if (err.code === "EADDRINUSE") {
261
+ rejectReady(new Error(`Port ${port} is already in use`));
262
+ } else {
263
+ rejectReady(new Error(`Server error: ${err.message}`));
264
+ }
234
265
  if (!resolved) {
235
266
  resolved = true;
236
- clearTimeout(timeout);
237
267
  rejectCallback(err);
238
268
  }
239
269
  });
240
270
 
241
- server.listen(port, "127.0.0.1", () => {
242
- const address = server?.address();
271
+ serverInstance.listen(port, "127.0.0.1", () => {
272
+ const address = serverInstance?.address();
243
273
  if (address && typeof address === "object") {
244
274
  actualPort = address.port;
245
275
  }
276
+ resolveReady(actualPort);
246
277
  });
247
-
278
+
248
279
  return {
249
- server,
280
+ server: { stop: stopServer },
250
281
  promise,
251
- getPort: () => {
252
- const address = server?.address();
253
- return address && typeof address === "object" ? address.port : 0;
254
- },
282
+ ready,
283
+ getPort: () => actualPort,
255
284
  };
256
285
  }
257
-
258
- /**
259
- * Get the actual port the server is listening on
260
- * @param server - HTTP server instance
261
- * @returns Port number
262
- */
263
- export function getServerPort(server: http.Server): number {
264
- const address = server.address();
265
- return address && typeof address === "object" ? address.port : 0;
266
- }
267
-