chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
INTROSPECT_TABLE_OUTPUT_PROPERTIES,
|
||||
type SupabaseColumnSchema,
|
||||
type SupabaseIntrospectParams,
|
||||
type SupabaseIntrospectResponse,
|
||||
type SupabaseTableSchema,
|
||||
} from '@/tools/supabase/types'
|
||||
import { supabaseBaseUrl } from '@/tools/supabase/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Tool for introspecting Supabase database schema.
|
||||
*
|
||||
* PostgREST (which powers `/rest/v1`) has no generic "run arbitrary SQL"
|
||||
* endpoint, so schema introspection is derived from the project's
|
||||
* auto-generated OpenAPI spec (`GET /rest/v1/` with an OpenAPI `Accept`
|
||||
* header) rather than a live `information_schema` query. Primary-key
|
||||
* detection is a best-effort naming heuristic (`id` column), and
|
||||
* foreign-key detection only succeeds if the table owner has added a
|
||||
* matching `references table.column` SQL comment — the OpenAPI spec does
|
||||
* not expose constraint metadata directly. Index information is not
|
||||
* available via this API at all. `nullable` is also best-effort: PostgREST
|
||||
* only lists a column under `required` when it's NOT NULL *and* has no
|
||||
* default, so a NOT NULL column with a default is reported as nullable.
|
||||
*/
|
||||
export const introspectTool: ToolConfig<SupabaseIntrospectParams, SupabaseIntrospectResponse> = {
|
||||
id: 'supabase_introspect',
|
||||
name: 'Supabase Introspect',
|
||||
description:
|
||||
'Introspect Supabase database schema from its OpenAPI spec to get table and column structures (best-effort primary/foreign key detection)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
projectId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
|
||||
},
|
||||
schema: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Database schema to introspect (defaults to all user schemas, commonly "public")',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Supabase service role secret key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${supabaseBaseUrl(params.projectId)}/rest/v1/`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
apikey: params.apiKey,
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
Accept: 'application/openapi+json',
|
||||
...(params.schema ? { 'Accept-Profile': params.schema } : {}),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: SupabaseIntrospectParams) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Failed to introspect database: ${errorText}`)
|
||||
}
|
||||
|
||||
const openApiSpec = await response.json()
|
||||
const tables = parseOpenApiSpec(openApiSpec, params?.schema)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: `Successfully introspected ${tables.length} table(s) from database schema`,
|
||||
tables,
|
||||
schemas: [...new Set(tables.map((t) => t.schema))],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
tables: {
|
||||
type: 'array',
|
||||
description: 'Array of table schemas with columns, keys, and indexes',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: INTROSPECT_TABLE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
schemas: { type: 'array', description: 'List of schemas found in the database' },
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a PostgREST-generated OpenAPI spec into table schemas.
|
||||
*
|
||||
* `isPrimaryKey` is a naming heuristic (`id` column) — PostgREST does not
|
||||
* expose real constraint metadata in the spec. `isForeignKey`/`references`
|
||||
* only populate when the table owner has added a `references table.column`
|
||||
* SQL comment on the column. `indexes` is always empty: index definitions
|
||||
* are not part of the OpenAPI spec.
|
||||
*/
|
||||
function parseOpenApiSpec(spec: any, filterSchema?: string): SupabaseTableSchema[] {
|
||||
const tables: SupabaseTableSchema[] = []
|
||||
const definitions = spec.definitions || spec.components?.schemas || {}
|
||||
|
||||
for (const [tableName, tableDef] of Object.entries(definitions)) {
|
||||
if (tableName.startsWith('_') || tableName === 'Error') continue
|
||||
|
||||
const definition = tableDef as any
|
||||
const properties = definition.properties || {}
|
||||
const required = definition.required || []
|
||||
|
||||
const columns: SupabaseColumnSchema[] = []
|
||||
const primaryKey: string[] = []
|
||||
const foreignKeys: Array<{
|
||||
column: string
|
||||
referencesTable: string
|
||||
referencesColumn: string
|
||||
}> = []
|
||||
|
||||
for (const [colName, colDef] of Object.entries(properties)) {
|
||||
const col = colDef as any
|
||||
const isPK = colName === 'id'
|
||||
const fkMatch = col.description?.match(/references\s+(\w+)\.(\w+)/)
|
||||
|
||||
const column: SupabaseColumnSchema = {
|
||||
name: colName,
|
||||
type: col.format || col.type || 'unknown',
|
||||
nullable: !required.includes(colName),
|
||||
default: col.default || null,
|
||||
isPrimaryKey: isPK,
|
||||
isForeignKey: !!fkMatch,
|
||||
}
|
||||
|
||||
if (fkMatch) {
|
||||
column.references = { table: fkMatch[1], column: fkMatch[2] }
|
||||
foreignKeys.push({
|
||||
column: colName,
|
||||
referencesTable: fkMatch[1],
|
||||
referencesColumn: fkMatch[2],
|
||||
})
|
||||
}
|
||||
|
||||
if (isPK) {
|
||||
primaryKey.push(colName)
|
||||
}
|
||||
|
||||
columns.push(column)
|
||||
}
|
||||
|
||||
tables.push({
|
||||
name: tableName,
|
||||
// The OpenAPI spec doesn't map tables to schemas, so this can only
|
||||
// reflect the schema that was actually requested (or "public" when
|
||||
// introspecting the default schema) — not necessarily the table's
|
||||
// true schema in a multi-schema database.
|
||||
schema: filterSchema || 'public',
|
||||
columns,
|
||||
primaryKey,
|
||||
foreignKeys,
|
||||
indexes: [],
|
||||
})
|
||||
}
|
||||
|
||||
return tables
|
||||
}
|
||||
Reference in New Issue
Block a user