Skip to content

fix(codegen): eliminate all CLI TypeScript compilation errors#782

Merged
pyramation merged 3 commits intomainfrom
devin/1772697625-fix-cli-codegen-types
Mar 5, 2026
Merged

fix(codegen): eliminate all CLI TypeScript compilation errors#782
pyramation merged 3 commits intomainfrom
devin/1772697625-fix-cli-codegen-types

Conversation

@pyramation
Copy link
Contributor

@pyramation pyramation commented Mar 5, 2026

fix(codegen): eliminate all CLI TypeScript compilation errors

Summary

Fixes CLI codegen templates in @constructive-io/graphql-codegen to produce type-correct TypeScript output, reducing generated CLI compilation errors from 3,143 → 0 without using as any, as never, // @ts-nocheck, or // @ts-ignore.

Template changes (6 files in graphql/codegen/src/core/codegen/cli/):

File Fix Errors fixed
utils-generator.ts Export FieldSchema type for table commands to import
table-command-generator.ts Import ORM input types (CreateXInput, XPatch), cast cleanedData to concrete types, FieldSchema type annotation, as string on PK lookups, exclude id/createdAt/updatedAt/nodeId from create data matching ORM's EXCLUDED_MUTATION_FIELDS, filter computed fields, fix handleGet detection ~3,000
executor-generator.ts Record<string, string> type on headers = {} ~30
command-map-generator.ts Typed createCommandMap return, as string on prompt results ~8
infra-generator.ts Double-cast prompt results via as unknown as Record<string, string>, as string/as boolean on field access ~4
custom-command-generator.ts Import XxxVariables types for ORM args (double-cast via unknown), import XxxPayloadSelect/XxxSelect types for { select } objects (double-cast via unknown) ~100

Type assertion strategy (no as never or as any)

  • ORM create data: stripUndefined(answers, fieldSchema) as CreateXInput['x'] — uses the actual ORM create input type
  • ORM update data: stripUndefined(answers, fieldSchema) as XPatch — uses the actual ORM patch type
  • Custom command args: answers as unknown as LoginVariables — double-cast through unknown to the concrete Variables type
  • Custom command select: { select: selectFields } as unknown as { select: LoginPayloadSelect } — double-cast to satisfy StrictSelect constraints
  • Prompt results: as string / as boolean on individual fields, as unknown as Record<string, string> on full results
  • PK lookups: answers.id as string — prompt values are always strings

Behavioral changes in generated code

  • handleGet restored for all real tables: Previously only 2/152 tables had handleGet due to a bug where table.query.one === null was treated as "no findOne". In reality, one: null just means no dedicated GraphQL findOne query — the ORM still generates findOne using the PK. Now handleGet is skipped only for pure read-only record types from SQL functions (where one, update, AND delete are all null) — affects only 3 tables: GetAllRecord, OrgGetManagersRecord, OrgGetSubordinatesRecord.
  • Tables with table.query.delete === null or table.query.update === null still correctly skip delete/update handlers.
  • Computed/generated fields (e.g. searchTsvRank, hashUuid) are excluded from create/update data objects.
  • Tables with auto-generated PKs (id) correctly exclude id from create data, matching the ORM's EXCLUDED_MUTATION_FIELDS list; natural-key PKs (e.g. NodeTypeRegistry.name) are still included.
  • All // @ts-nocheck directives removed from generated files.

All 54 codegen tests pass. 46 snapshots pass.

Updates since last revision

  1. Fixed critical handleGet bug: The initial fix incorrectly removed handleGet from 150/152 tables. The check table.query?.one !== null was wrong because almost all tables have one: null (no dedicated GraphQL findOne query), but the ORM still generates findOne using the PK. Now correctly generates handleGet for all tables except the 3 pure record types from SQL functions where the ORM truly doesn't generate findOne.

  2. Replaced all as never casts with proper ORM input type imports (CreateXInput['x'], XPatch, XxxVariables, XxxPayloadSelect). No as never or as any remains anywhere in generated code.

  3. Fixed PK exclusion logic: Now matches the ORM's EXCLUDED_MUTATION_FIELDS = ['id', 'createdAt', 'updatedAt', 'nodeId'] instead of relying on CleanField.hasDefault (which was null/undefined for some tables). This fixes TS2353 errors for RoleType, MembershipType, Object, SqlMigration, AstMigration.

  4. Verified zero forbidden casts: Grepped generated code for as never|as any|@ts-ignore|@ts-nocheck — all clean.

Review & Testing Checklist for Human

Risk level: 🟡 YELLOW — Template changes are localized but blast radius is large (240+ generated files). Type assertions use concrete types but some use double-cast through unknown.

  • Verify handleGet heuristic is sound: The detection logic hasGet = one !== null || hasUpdate || hasDelete assumes that tables with update/delete mutations always have findOne. If a table has update/delete but no findOne in the ORM, the CLI will generate a broken handleGet. Check a few edge cases (e.g., views with triggers that allow updates) to confirm the ORM always generates findOne when it generates update.

  • Verify ORM_EXCLUDED_FIELDS matches ORM behavior: Confirm that ['id', 'createdAt', 'updatedAt', 'nodeId'] matches the ORM's EXCLUDED_MUTATION_FIELDS in input-types-generator.ts. If these diverge, create operations will fail at runtime.

  • Runtime smoke test (recommended): Generate CLI for a test schema, run a few get/create/update commands with various field types (enums, arrays, booleans, numbers) to verify type assertions hold at runtime. The TypeScript build passes, but runtime validation is the ultimate test.

Notes

  • Session: https://app.devin.ai/sessions/0852119907284bb4a09c1910ef213240
  • Requested by: @pyramation
  • The diff is large because it includes regenerated CLI code for 148 tables across 4 APIs. The actual template changes are only 6 files.
  • CI verifies that the codegen tests pass and the constructive-cli package builds without errors (0 TS errors confirmed locally).
  • The relative import path logic (../../../orm/ vs ../../orm/) was explained in the user conversation — it's correct for single vs multi-target layouts.

Open with Devin

Fix all CLI codegen template type errors (3,143 → 0) without using
`as any` casts or `// @ts-nocheck` suppression.

Changes by category:
- fieldSchema: add FieldSchema type annotation (fixes ~1,028 TS2345)
- executor headers: add Record<string, string> type (fixes ~30 TS2339)
- prompt results: cast to Record<string, string> (fixes ~100 TS2345/TS2322)
- ORM data objects: use `as never` universal cast (fixes ~2,000 TS2322)
- commandMap: add Record<string, CommandHandler> type (fixes ~4 TS7053)
- array fields: uniform handling via `as never` on data objects
- read-only views: skip get/update/delete for null query operations
- computed fields: filter via getWritableFieldNames
- custom command args: cast through `as never` for Variables types
- infra prompt: double-cast through unknown for Record<string, string>
- custom command select: cast { select } through never for StrictSelect

All 54 codegen tests pass. 9 snapshots updated.
Regenerated CLI code builds with 0 TypeScript errors.
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

- Import and cast { select } to specific Select types (e.g. CheckPasswordPayloadSelect)
  instead of Record<string, unknown> to satisfy StrictSelect constraints
- Match ORM's EXCLUDED_MUTATION_FIELDS list to exclude 'id' from create data
  for tables with auto-generated PKs (RoleType, MembershipType, Object, etc.)
- Allow non-'id' PKs like NodeTypeRegistry.name in create data
- Use CleanField column-level info instead of unreliable introspection defaults
- Update test snapshots (54 tests pass)
- Regenerate CLI code: 0 TypeScript errors, 0 as-never/as-any/ts-ignore casts
…r pure record types

- handleGet was incorrectly removed for 150/152 tables because table.query.one
  is null for most tables (no dedicated GraphQL findOne query), but the ORM still
  generates findOne using the PK.
- Now: hasGet = true unless one, update, AND delete are all null (pure read-only
  record types like GetAllRecord, OrgGetManagersRecord, OrgGetSubordinatesRecord).
- 0 TypeScript errors, 0 forbidden casts, 54 codegen tests pass.
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

// Determine which operations the ORM model supports for this table.
// Most tables have `one: null` simply because there's no dedicated GraphQL
// findOne query, but the ORM still generates `findOne` using the PK.
// The only tables WITHOUT `findOne` are pure record types from SQL functions
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 hasGet check uses !== null without guarding against undefined, incorrectly enabling 'get' when table.query is undefined

At graphql/codegen/src/core/codegen/cli/table-command-generator.ts:799, hasGet is computed as table.query?.one !== null. When table.query is undefined (which is valid per CleanTable interface at graphql/codegen/src/types/schema.ts:18), the optional chaining evaluates to undefined, and undefined !== null is true. This means hasGet will be true even when the table has no query information at all, causing the generator to emit a get subcommand and handleGet function for tables that may not support findOne. In contrast, hasUpdate and hasDelete on lines 800-801 correctly check !== undefined && !== null, so they don't have this problem.

Suggested change
// The only tables WITHOUT `findOne` are pure record types from SQL functions
const hasGet = table.query?.one !== undefined && table.query?.one !== null;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@pyramation pyramation merged commit 8dd3cb9 into main Mar 5, 2026
43 checks passed
@pyramation pyramation deleted the devin/1772697625-fix-cli-codegen-types branch March 5, 2026 20:12
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