postgresai 0.14.0-dev.72 → 0.14.0-dev.74
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 +1 -1
- package/dist/bin/postgres-ai.js +12 -7
- package/lib/metrics-embedded.ts +1 -1
- package/lib/supabase.ts +18 -7
- package/package.json +1 -1
- package/test/supabase.test.ts +59 -0
package/bin/postgres-ai.ts
CHANGED
|
@@ -760,7 +760,7 @@ program
|
|
|
760
760
|
// Fetch database URL for JSON output (non-blocking, best-effort)
|
|
761
761
|
let databaseUrl: string | null = null;
|
|
762
762
|
if (jsonOutput) {
|
|
763
|
-
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig);
|
|
763
|
+
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig, opts.monitoringUser);
|
|
764
764
|
}
|
|
765
765
|
|
|
766
766
|
try {
|
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13064,7 +13064,7 @@ var {
|
|
|
13064
13064
|
// package.json
|
|
13065
13065
|
var package_default = {
|
|
13066
13066
|
name: "postgresai",
|
|
13067
|
-
version: "0.14.0-dev.
|
|
13067
|
+
version: "0.14.0-dev.74",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -15887,7 +15887,7 @@ var Result = import_lib.default.Result;
|
|
|
15887
15887
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
15888
15888
|
var defaults = import_lib.default.defaults;
|
|
15889
15889
|
// package.json
|
|
15890
|
-
var version = "0.14.0-dev.
|
|
15890
|
+
var version = "0.14.0-dev.74";
|
|
15891
15891
|
var package_default2 = {
|
|
15892
15892
|
name: "postgresai",
|
|
15893
15893
|
version,
|
|
@@ -25312,8 +25312,12 @@ class SupabaseClient {
|
|
|
25312
25312
|
return err;
|
|
25313
25313
|
}
|
|
25314
25314
|
}
|
|
25315
|
-
async function fetchPoolerDatabaseUrl(config2) {
|
|
25315
|
+
async function fetchPoolerDatabaseUrl(config2, username) {
|
|
25316
25316
|
const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config2.projectRef)}/config/database/pooler`;
|
|
25317
|
+
const effectiveUsername = (() => {
|
|
25318
|
+
const suffix = `.${config2.projectRef}`;
|
|
25319
|
+
return username.endsWith(suffix) ? username : `${username}${suffix}`;
|
|
25320
|
+
})();
|
|
25317
25321
|
try {
|
|
25318
25322
|
const response = await fetch(url, {
|
|
25319
25323
|
method: "GET",
|
|
@@ -25327,13 +25331,14 @@ async function fetchPoolerDatabaseUrl(config2) {
|
|
|
25327
25331
|
const data = await response.json();
|
|
25328
25332
|
if (Array.isArray(data) && data.length > 0) {
|
|
25329
25333
|
const pooler = data[0];
|
|
25330
|
-
if (pooler.db_host && pooler.db_port && pooler.db_name
|
|
25331
|
-
return `postgresql://${
|
|
25334
|
+
if (pooler.db_host && pooler.db_port && pooler.db_name) {
|
|
25335
|
+
return `postgresql://${effectiveUsername}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
|
|
25332
25336
|
}
|
|
25333
25337
|
if (typeof pooler.connection_string === "string") {
|
|
25334
25338
|
try {
|
|
25335
25339
|
const connUrl = new URL(pooler.connection_string);
|
|
25336
|
-
|
|
25340
|
+
const portPart = connUrl.port ? `:${connUrl.port}` : "";
|
|
25341
|
+
return `postgresql://${effectiveUsername}@${connUrl.hostname}${portPart}${connUrl.pathname}`;
|
|
25337
25342
|
} catch {
|
|
25338
25343
|
return null;
|
|
25339
25344
|
}
|
|
@@ -28004,7 +28009,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
28004
28009
|
const supabaseClient = new SupabaseClient(supabaseConfig);
|
|
28005
28010
|
let databaseUrl = null;
|
|
28006
28011
|
if (jsonOutput) {
|
|
28007
|
-
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig);
|
|
28012
|
+
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig, opts.monitoringUser);
|
|
28008
28013
|
}
|
|
28009
28014
|
try {
|
|
28010
28015
|
const database = await supabaseClient.getCurrentDatabase();
|
package/lib/metrics-embedded.ts
CHANGED
package/lib/supabase.ts
CHANGED
|
@@ -335,16 +335,26 @@ export class SupabaseClient {
|
|
|
335
335
|
|
|
336
336
|
/**
|
|
337
337
|
* Fetch the database pooler connection string from Supabase Management API.
|
|
338
|
-
* Returns a postgresql:// URL with username but no password.
|
|
338
|
+
* Returns a postgresql:// URL with the specified username but no password.
|
|
339
339
|
*
|
|
340
340
|
* @param config Supabase configuration with projectRef and accessToken
|
|
341
|
-
* @
|
|
341
|
+
* @param username Username to include in the URL (e.g., monitoring user)
|
|
342
|
+
* @returns Database URL without password (e.g., "postgresql://user@host:port/postgres")
|
|
342
343
|
*/
|
|
343
344
|
export async function fetchPoolerDatabaseUrl(
|
|
344
|
-
config: SupabaseConfig
|
|
345
|
+
config: SupabaseConfig,
|
|
346
|
+
username: string
|
|
345
347
|
): Promise<string | null> {
|
|
346
348
|
const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config.projectRef)}/config/database/pooler`;
|
|
347
349
|
|
|
350
|
+
// For Supabase pooler connections, the username must include the project ref:
|
|
351
|
+
// <user>.<project_ref>
|
|
352
|
+
// Example:
|
|
353
|
+
// postgresql://postgres_ai_mon.xhaqmsvczjkkvkgdyast@aws-1-eu-west-1.pooler.supabase.com:6543/postgres
|
|
354
|
+
const effectiveUsername = (() => {
|
|
355
|
+
const suffix = `.${config.projectRef}`;
|
|
356
|
+
return username.endsWith(suffix) ? username : `${username}${suffix}`;
|
|
357
|
+
})();
|
|
348
358
|
try {
|
|
349
359
|
const response = await fetch(url, {
|
|
350
360
|
method: "GET",
|
|
@@ -364,15 +374,16 @@ export async function fetchPoolerDatabaseUrl(
|
|
|
364
374
|
if (Array.isArray(data) && data.length > 0) {
|
|
365
375
|
const pooler = data[0];
|
|
366
376
|
// Build URL from components if available
|
|
367
|
-
if (pooler.db_host && pooler.db_port && pooler.db_name
|
|
368
|
-
return `postgresql://${
|
|
377
|
+
if (pooler.db_host && pooler.db_port && pooler.db_name) {
|
|
378
|
+
return `postgresql://${effectiveUsername}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
|
|
369
379
|
}
|
|
370
380
|
// Fallback: try to extract from connection_string if present
|
|
371
381
|
if (typeof pooler.connection_string === "string") {
|
|
372
382
|
try {
|
|
373
383
|
const connUrl = new URL(pooler.connection_string);
|
|
374
|
-
//
|
|
375
|
-
|
|
384
|
+
// Use provided username; handle empty port for default ports (e.g., 5432)
|
|
385
|
+
const portPart = connUrl.port ? `:${connUrl.port}` : "";
|
|
386
|
+
return `postgresql://${effectiveUsername}@${connUrl.hostname}${portPart}${connUrl.pathname}`;
|
|
376
387
|
} catch {
|
|
377
388
|
return null;
|
|
378
389
|
}
|
package/package.json
CHANGED
package/test/supabase.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, expect, test, beforeEach, afterEach, mock } from "bun:test";
|
|
|
2
2
|
import {
|
|
3
3
|
resolveSupabaseConfig,
|
|
4
4
|
extractProjectRefFromUrl,
|
|
5
|
+
fetchPoolerDatabaseUrl,
|
|
5
6
|
SupabaseClient,
|
|
6
7
|
applyInitPlanViaSupabase,
|
|
7
8
|
verifyInitSetupViaSupabase,
|
|
@@ -137,6 +138,64 @@ describe("Supabase module", () => {
|
|
|
137
138
|
});
|
|
138
139
|
});
|
|
139
140
|
|
|
141
|
+
describe("fetchPoolerDatabaseUrl", () => {
|
|
142
|
+
const originalFetch = globalThis.fetch;
|
|
143
|
+
|
|
144
|
+
afterEach(() => {
|
|
145
|
+
globalThis.fetch = originalFetch;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("returns pooler db url with username including project ref (db_host/db_port/db_name response)", async () => {
|
|
149
|
+
globalThis.fetch = mock(() =>
|
|
150
|
+
Promise.resolve(
|
|
151
|
+
new Response(
|
|
152
|
+
JSON.stringify([
|
|
153
|
+
{
|
|
154
|
+
db_host: "aws-1-eu-west-1.pooler.supabase.com",
|
|
155
|
+
db_port: 6543,
|
|
156
|
+
db_name: "postgres",
|
|
157
|
+
},
|
|
158
|
+
]),
|
|
159
|
+
{ status: 200 }
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
) as unknown as typeof fetch;
|
|
163
|
+
|
|
164
|
+
const url = await fetchPoolerDatabaseUrl(
|
|
165
|
+
{ projectRef: "xhaqmsvczjkkvkgdyast", accessToken: "token" },
|
|
166
|
+
"postgres_ai_mon"
|
|
167
|
+
);
|
|
168
|
+
expect(url).toBe(
|
|
169
|
+
"postgresql://postgres_ai_mon.xhaqmsvczjkkvkgdyast@aws-1-eu-west-1.pooler.supabase.com:6543/postgres"
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("does not double-append project ref if username already has it", async () => {
|
|
174
|
+
globalThis.fetch = mock(() =>
|
|
175
|
+
Promise.resolve(
|
|
176
|
+
new Response(
|
|
177
|
+
JSON.stringify([
|
|
178
|
+
{
|
|
179
|
+
db_host: "aws-1-eu-west-1.pooler.supabase.com",
|
|
180
|
+
db_port: 6543,
|
|
181
|
+
db_name: "postgres",
|
|
182
|
+
},
|
|
183
|
+
]),
|
|
184
|
+
{ status: 200 }
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
) as unknown as typeof fetch;
|
|
188
|
+
|
|
189
|
+
const url = await fetchPoolerDatabaseUrl(
|
|
190
|
+
{ projectRef: "xhaqmsvczjkkvkgdyast", accessToken: "token" },
|
|
191
|
+
"postgres_ai_mon.xhaqmsvczjkkvkgdyast"
|
|
192
|
+
);
|
|
193
|
+
expect(url).toBe(
|
|
194
|
+
"postgresql://postgres_ai_mon.xhaqmsvczjkkvkgdyast@aws-1-eu-west-1.pooler.supabase.com:6543/postgres"
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
140
199
|
describe("SupabaseClient", () => {
|
|
141
200
|
test("throws error when project ref is empty", () => {
|
|
142
201
|
expect(() => new SupabaseClient({ projectRef: "", accessToken: "token" })).toThrow(
|