fix(schema-generator): sanitize database identifiers in TypeScript output path#3510
Conversation
…cript output path Add sanitizeIdentifier() to strip/escape non-alphanumeric characters from database column names and enum type names before emitting them as TypeScript property keys. This matches the existing cleanMappedName behavior in the GraphQL-SDL output path. Changes: - Replace createIdentifier(field.name) with createStringLiteral(sanitizeIdentifier(field.name)) in createProperty() for model field properties - Replace createIdentifier(toPascalCase([type.name])) with createStringLiteral(toPascalCase([sanitizeIdentifier(type.name)])) in createEnums() - Sanitize enum type name in a.ref() calls for consistency - Fix enum deduplication to work with StringLiteral property names - Add unit tests verifying sanitization of dangerous column/enum names - Update existing snapshots to reflect quoted property key format
E2E & PR Checks Results ✅Both batches completed successfully: E2E batch (
PR checks batch ( Ready for merge. |
| if (!name?.match(/[a-zA-Z]/)) { | ||
| const suffix = name?.replace(/[^0-9]+/g, '') ?? ''; | ||
| return isField ? `field${suffix}` : `Model${suffix}`; | ||
| } |
There was a problem hiding this comment.
Even though unlikely, if multiple fields contain no letter, and no number, they will result in the same name field. Shall we add a randomized suffix?
There was a problem hiding this comment.
I think that equates to a bad state. Randomization doesn't give an anchor point to code against.
There was a problem hiding this comment.
Fair point — randomization is the wrong call; a non-deterministic name would churn on every regen and give consumers nothing stable to code against. Withdrawing that suggestion.
That said, the underlying collision is still real: two all-special-character columns (e.g. () and @#$) both collapse to field, producing duplicate property keys. Since a column with zero alphanumeric characters is arguably a bad-state input anyway, a good follow-up would be to error with a clear message (pointing the user at an explicit rename/@mapsTo) rather than silently coercing. Fine to leave that for a separate change — flagging it so we don't lose the thread.
Summary
Sanitizes database column names and enum type names in the TypeScript data schema output path, matching the existing
cleanMappedNamebehavior in the GraphQL-SDL output path.Problem
The TypeScript schema generator passed raw database column names and enum type names directly to
ts.factory.createIdentifier(), which emits them as bare JavaScript identifiers without any sanitization. Names containing special characters (parentheses, brackets, quotes, etc.) could produce invalid or unsafe TypeScript output.Changes
sanitizeIdentifier()helper that strips leading non-alphabetic characters and replaces non-alphanumeric characters with underscores (matchingcleanMappedNamefrom the GraphQL-SDL path)createStringLiteralinstead ofcreateIdentifierfor field property names increateProperty()createStringLiteralinstead ofcreateIdentifierfor enum type property names increateEnums()a.ref()calls for consistency between the enum definition and its referencesStringLiteralproperty names (uses.textinstead of.escapedText)Defense in Depth
This is a defense-in-depth measure. The TS output path should apply the same sanitization as the GraphQL-SDL path to ensure consistent, safe output regardless of database column naming.
Testing