Skip to content

fix(pg-many-to-many): auto-disambiguate type name collisions instead of crashing#1059

Merged
pyramation merged 3 commits intomainfrom
fix/m2m-type-name-collision
May 6, 2026
Merged

fix(pg-many-to-many): auto-disambiguate type name collisions instead of crashing#1059
pyramation merged 3 commits intomainfrom
fix/m2m-type-name-collision

Conversation

@pyramation
Copy link
Copy Markdown
Contributor

@pyramation pyramation commented May 6, 2026

Summary

Fixes the pg-many-to-many plugin crash when two different FK paths from table A to table B produce the same GraphQL edge type name (e.g. AppBucketAppFilesManyToManyEdge).

Root cause: The custom _manyToManyRelation inflector in custom-inflector.ts had a counting bug — it skipped junction tables where the junction IS the right table (rel.remoteResource?.codec?.name !== rightTable.codec.name). When files acts as both junction and right table (self-referential FK), it wasn't counted, so both paths got the simplified name → same type name → crash.

Two fixes applied:

  1. Inflector counting fix (custom-inflector.ts): Removed the !== rightTable.codec.name condition so self-junction paths are properly counted. When multiple m2m paths to the same right table exist, the inflector falls back to the verbose (unique) naming.

  2. Defense-in-depth (many-to-many-preset.ts): Added a build hook that wraps registerObjectType to catch type naming conflicts specifically for many-to-many types and silently skip the duplicate. This prevents crashes even in unforeseen edge cases.

Why opt-in didn't prevent this: The ManyToManyOptInPlugin gates field creation via pgManyToMany behavior, but the upstream plugin's init hook calls createManyToManyConnectionType (which registers types) for all discovered relationships unconditionally — before behavior filtering. So even with -manyToMany default, types still get registered and can collide.

Closes constructive-io/constructive-planning#797

Review & Testing Checklist for Human

  • Verify the inflector change doesn't break existing m2m type names in your schema (run your app's GraphQL introspection and compare type names before/after)
  • Confirm the defense-in-depth registerObjectType wrapper doesn't silently swallow legitimate type conflicts in non-m2m scenarios (it only catches errors whose origin string contains "many-to-many")

Notes

  • The integration test adds buckets, files (with self-referential parent_id FK), and file_events tables to the existing preset-integration.test.ts to reproduce the exact collision scenario from the issue
  • Tests verify: schema builds without crash, m2m edge types are unique (no duplicates), and m2m types exist in the schema when opted-in via @behavior +manyToMany
  • All 51 CI checks pass

Link to Devin session: https://app.devin.ai/sessions/1db04105a50342578181794804059512
Requested by: @pyramation

…of crashing

Two fixes for the many-to-many plugin type naming collision:

1. Fix inflector counting logic in _manyToManyRelation to properly count
   self-junction tables (where junction === rightTable). Previously, the
   condition excluded these paths, so two junction tables targeting the
   same left→right pair would both get the simplified name, causing a
   crash in graphile-build's register().

2. Add defense-in-depth in ManyToManyOptInPlugin: wrap registerObjectType
   to catch type naming conflicts for many-to-many types and silently
   skip duplicates. This prevents crashes even if the inflector produces
   a collision in some unforeseen edge case.

Closes constructive-io/constructive-planning#797
@devin-ai-integration
Copy link
Copy Markdown
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

pyramation added 2 commits May 6, 2026 06:30
The m2m connection field name doesn't contain 'connection' — that
suffix is only on the GraphQL type. Check for Bucket*ManyToManyEdge
types in the schema introspection instead.
@pyramation pyramation merged commit 73bc96b into main May 6, 2026
53 checks passed
@pyramation pyramation deleted the fix/m2m-type-name-collision branch May 6, 2026 06:49
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