Skip to content

feat: Add a command to generate tanstack-db definitions#5838

Open
ivasilov wants to merge 2 commits into
developfrom
feat/gen-tanstack-db
Open

feat: Add a command to generate tanstack-db definitions#5838
ivasilov wants to merge 2 commits into
developfrom
feat/gen-tanstack-db

Conversation

@ivasilov

@ivasilov ivasilov commented Jul 9, 2026

Copy link
Copy Markdown
Member

No description provided.

@ivasilov ivasilov force-pushed the feat/gen-tanstack-db branch from 71d95c9 to aa97421 Compare July 9, 2026 10:26
@ivasilov ivasilov marked this pull request as ready for review July 9, 2026 10:28
@ivasilov ivasilov requested a review from a team as a code owner July 9, 2026 10:28
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Supabase CLI preview

npx --yes https://pkg.pr.new/supabase/cli/supabase@c13860165c158bbae33801cddea1747c2a428ee3

Preview package for commit c138601.

} else if (prop.format === "date-time" || prop.format === "timestamp with time zone") {
zodType = "z.string()";
} else if (prop.enum !== undefined && prop.enum.length > 0) {
const enumValues = prop.enum.map((value) => `'${value.replaceAll("'", "\\'")}'`).join(", ");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Severity: MEDIUM

Code injection via incomplete enum-value escaping. Backslashes are not escaped before single-quotes. A Postgres enum value like \', z.string().default(process.env.SECRET)// (backslash + quote + payload) produces generated TS text '\\', z.string().default(process.env.SECRET)//' — the \\ is parsed as an escaped backslash, the ' then closes the string, and the payload sits outside it as executable TypeScript code.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Escape backslashes before escaping single-quotes. The root cause is that a \ preceding a ' in the enum value turns the \' escape sequence in the generated TypeScript into a literal backslash (\\) followed by a string-terminating '. Fix this by chaining a .replaceAll("\\\\" , "\\\\\\\\") call first so backslashes are doubled, then escaping single-quotes as before. This ensures \' in the output is always parsed as an escaped backslash + escaped quote, never as an escape of the closing delimiter.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
const enumValues = prop.enum.map((value) => `'${value.replaceAll("'", "\\'")}'`).join(", ");
const enumValues = prop.enum.map((value) => `'${value.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\'")}'`).join(", ");

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa97421996

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


const documents = yield* Effect.forEach(schemas, (schema) =>
api.v1
.getDatabaseOpenapi({ ref, schema })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Decode the raw remote OpenAPI response

For the linked/project-id paths, this high-level API call has already decoded the HTTP body with V1GetDatabaseOpenapiOutput, which is currently Schema.Struct({}) in packages/api/src/generated/contracts.ts:2327 and is applied by schemaBodyJson in packages/api/src/internal/client.ts:482. Effect Schema drops properties not declared in the struct by default, so the real definitions payload is stripped before legacyDecodeOpenApiDefinitions runs; remote generation then sees {} and fails with “no tables found” even when the project has tables. Use a raw response path or a preserving output schema for this endpoint.

Useful? React with 👍 / 👎.


return [
`export const ${collectionName} = createCollection(supabaseCollectionOptions({`,
` tableName: ${JSON.stringify(tableName)},`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve schema names when emitting collections

When users pass --schema auth/another non-public schema, or the config includes one, the handler fetches that schema's OpenAPI document but the generator emits only tableName against a single default Supabase client. That generated collection will query/mutate the client's default PostgREST schema (normally public) rather than the schema that was introspected, and legacyMergeOpenApiDefinitions has already discarded the schema name so collisions and routing cannot be recovered later.

Useful? React with 👍 / 👎.

zodType = `z.array(${legacyOpenApiItemTypeToZod(prop.items)})`;
break;
case "object":
zodType = "z.record(z.unknown())";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Generate Zod 4-compatible record schemas

For any table with a JSON/object column, the generated file contains z.record(z.unknown()). Zod 4's public API requires a key schema and value schema for records (for example z.record(z.string(), z.unknown())), so users installing the current zod package get generated code that fails to type-check/execute as soon as their schema includes an object column.

Useful? React with 👍 / 👎.

Comment on lines +116 to +119
const loaded =
requestedSchemas.length > 0 ? null : yield* loadProjectConfig(cliConfig.workdir);
const schemas =
requestedSchemas.length > 0 ? requestedSchemas : defaultSchemas(loaded?.config.api.schemas);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply remote config overrides for default schemas

When no --schema is passed and the selected linked/project-id ref has a matching [remotes.*] override in supabase/config.toml, this loads the base config without passing the resolved ref, so the remote override's api.schemas is ignored. In that setup the command introspects the base/public schemas instead of the schemas configured for the selected remote project, producing missing or wrong collections unless the user manually repeats --schema.

Useful? React with 👍 / 👎.

properties: Record<string, LegacyOpenApiProperty>,
): ReadonlyArray<string> {
const primaryKeys = Object.entries(properties)
.filter(([, prop]) => prop.description?.toLowerCase().includes("primary key") === true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match the exact primary-key marker

If a non-primary-key column has a user description/comment that happens to include the words “primary key”, this substring check treats it as part of the collection key and suppresses the id fallback. The generated collection then derives identities and update/delete filters from the wrong column; check for PostgREST's actual <pk/> marker (or equivalent structured signal) instead of any descriptive text.

Useful? React with 👍 / 👎.

Comment on lines +186 to +188
const safeTableId = legacySanitizeIdentifier(tableName);
const typeName = legacyToPascalCase(safeTableId);
const schemaName = `${legacyToCamelCase(safeTableId)}Schema`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect sanitized table-name collisions

When two valid exposed table names normalize to the same identifier, such as quoted foo-bar and foo_bar (or case variants), both tables emit the same schema/type/collection export names. The generated file then fails to compile or exposes an ambiguous collection set, so the generator should track generated identifiers and either disambiguate or fail with a clear collision error.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant