Repository navigation
chore(scripts): read the models catalog from Neon instead of Supabase #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| /** | ||
| * Regenerate src/data/ai-models.json from the Supabase ai_models catalog. | ||
| * Regenerate src/data/ai-models.json from the Mogplex ai_models catalog in | ||
| * Neon. Needs a read-capable DATABASE_URL in .env.local. | ||
| * | ||
| * pnpm gen:models | ||
| * | ||
|
|
@@ -9,36 +10,43 @@ | |
|
|
||
| import { mkdir, writeFile } from 'node:fs/promises'; | ||
| import { dirname, resolve } from 'node:path'; | ||
| import { Client } from 'pg'; | ||
|
|
||
| function requireEnv(name: string): string { | ||
| const value = process.env[name]; | ||
| if (!value) { | ||
| console.error(`Missing ${name}. Run \`vercel env pull .env.local\` first.`); | ||
| console.error(`Missing ${name}. Add it to .env.local first.`); | ||
| process.exit(1); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| const SUPABASE_URL = requireEnv('NEXT_PUBLIC_SUPABASE_URL'); | ||
| const SUPABASE_ANON_KEY = requireEnv('NEXT_PUBLIC_SUPABASE_ANON_KEY'); | ||
| const DATABASE_URL = requireEnv('DATABASE_URL'); | ||
|
|
||
| const OUTPUT = resolve(process.cwd(), 'src/data/ai-models.json'); | ||
|
|
||
| const SELECT = [ | ||
| 'id', | ||
| 'provider', | ||
| 'name', | ||
| 'context_length', | ||
| 'capabilities', | ||
| 'pricing_input', | ||
| 'pricing_output', | ||
| 'pricing_cache_read', | ||
| 'pricing_cache_write', | ||
| 'is_available', | ||
| 'is_hidden', | ||
| 'is_recommended', | ||
| 'recommendation_bucket', | ||
| ].join(','); | ||
| // numeric columns are cast to float8 so node-pg returns JSON numbers, matching | ||
| // the snapshot shape the PostgREST export produced. | ||
| const QUERY = ` | ||
| select | ||
| id, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider testing the query independently of the live database
|
||
| provider, | ||
| name, | ||
| context_length, | ||
| capabilities, | ||
| pricing_input::float8 as pricing_input, | ||
| pricing_output::float8 as pricing_output, | ||
| pricing_cache_read::float8 as pricing_cache_read, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Note the row-count jump before the snapshot is refreshed
|
||
| pricing_cache_write::float8 as pricing_cache_write, | ||
| is_available, | ||
| is_hidden, | ||
| is_recommended, | ||
| recommendation_bucket | ||
| from public.ai_models | ||
| where is_hidden = false and is_available = true | ||
| order by provider asc, name asc | ||
| limit $1 | ||
| `; | ||
|
|
||
| type Row = { | ||
| id: string; | ||
|
|
@@ -59,28 +67,17 @@ type Row = { | |
| const PAGE_SIZE = 10_000; | ||
|
|
||
| async function main() { | ||
| const url = new URL(`${SUPABASE_URL}/rest/v1/ai_models`); | ||
| url.searchParams.set('select', SELECT); | ||
| url.searchParams.set('is_hidden', 'eq.false'); | ||
| url.searchParams.set('is_available', 'eq.true'); | ||
| url.searchParams.set('order', 'provider.asc,name.asc'); | ||
| url.searchParams.set('limit', String(PAGE_SIZE)); | ||
|
|
||
| const res = await fetch(url, { | ||
| headers: { | ||
| apikey: SUPABASE_ANON_KEY, | ||
| Authorization: `Bearer ${SUPABASE_ANON_KEY}`, | ||
| }, | ||
| }); | ||
|
|
||
| if (!res.ok) { | ||
| throw new Error(`Supabase REST returned ${res.status} ${res.statusText}`); | ||
| const client = new Client({ connectionString: DATABASE_URL }); | ||
| await client.connect(); | ||
| let rows: Row[]; | ||
| try { | ||
| rows = (await client.query<Row>(QUERY, [PAGE_SIZE])).rows; | ||
| } finally { | ||
| await client.end(); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider an explicit
|
||
|
|
||
| const rows = (await res.json()) as Row[]; | ||
|
|
||
| if (rows.length === 0) { | ||
| throw new Error('No rows returned — check RLS policies or filters.'); | ||
| throw new Error('No rows returned — check the filters and DATABASE_URL.'); | ||
| } | ||
| if (rows.length >= PAGE_SIZE) { | ||
| throw new Error( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Warning: Document the required
DATABASE_URLshape (sslmode) and prefer a read-only rolenew Client({ connectionString: DATABASE_URL })carries no TLS configuration of its own.ssl: falseunless the connection string carriessslmode=…(orPGSSLMODEis set), and Neon "rejects connections that do not use SSL/TLS" — so aDATABASE_URLcopied without the query parameter fails with a confusing server-side error instead of the script's own "Missing DATABASE_URL" message.postgresql://<user>:<pass>@<neon-host>/<db>?sslmode=require, in the script header comment and/or the PR description.requireEnvfailure message only says "Add it to .env.local first"; make it say where to get the URL and thatsslmodeis required.selectonpublic.ai_models; prefer a dedicated role withGRANT SELECTon that table over handing every contributor a full production credential, since the connection string now carries a real password instead of the old public anon key..env*.localis gitignored soDATABASE_URLstays out of the repo, andpnpm-lock.yamlis updated sopnpm install --frozen-lockfilein CI still resolves.