feat: Add a command to generate tanstack-db definitions#5838
Conversation
71d95c9 to
aa97421
Compare
Supabase CLI previewnpx --yes https://pkg.pr.new/supabase/cli/supabase@c13860165c158bbae33801cddea1747c2a428ee3Preview package for commit |
| } 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(", "); |
There was a problem hiding this comment.
🟡 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.
| const enumValues = prop.enum.map((value) => `'${value.replaceAll("'", "\\'")}'`).join(", "); | |
| const enumValues = prop.enum.map((value) => `'${value.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\'")}'`).join(", "); |
There was a problem hiding this comment.
💡 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 }) |
There was a problem hiding this comment.
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)},`, |
There was a problem hiding this comment.
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())"; |
There was a problem hiding this comment.
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 👍 / 👎.
| const loaded = | ||
| requestedSchemas.length > 0 ? null : yield* loadProjectConfig(cliConfig.workdir); | ||
| const schemas = | ||
| requestedSchemas.length > 0 ? requestedSchemas : defaultSchemas(loaded?.config.api.schemas); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 👍 / 👎.
| const safeTableId = legacySanitizeIdentifier(tableName); | ||
| const typeName = legacyToPascalCase(safeTableId); | ||
| const schemaName = `${legacyToCamelCase(safeTableId)}Schema`; |
There was a problem hiding this comment.
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 👍 / 👎.
No description provided.