Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: fix up foreign key columns with custom object#12042

Open
gioboa wants to merge 1 commit intotypeorm:masterfrom
gioboa:fix/9565
Open

fix: fix up foreign key columns with custom object#12042
gioboa wants to merge 1 commit intotypeorm:masterfrom
gioboa:fix/9565

Conversation

@gioboa
Copy link
Collaborator

@gioboa gioboa commented Feb 24, 2026

Close #9565

Description of change

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

@qodo-free-for-open-source-projects

User description

Close #9565

Description of change

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

PR Type

Bug fix, Tests


Description

  • Fix foreign key columns with custom object transformers

  • Prevent recursive entity value extraction when transformer exists

  • Add test entities with custom object ID types and relationships

  • Verify foreign key handling with transformed custom objects


Diagram Walkthrough

flowchart LR
  A["Column with Transformer"] -->|Check transformer exists| B["Skip recursive extraction"]
  C["Foreign Key Column"] -->|Has custom object| D["Prevent double transformation"]
  E["Test: Article + Author"] -->|Custom ID objects| F["Verify correct persistence"]
Loading

File Walkthrough

Relevant files
Bug fix
ColumnMetadata.ts
Skip entity extraction when transformer present                   

src/metadata/ColumnMetadata.ts

  • Add !this.transformer check to prevent recursive entity value
    extraction
  • Applied to both embedded object and regular entity property handling
  • Prevents double transformation when column has custom transformer
+4/-2     
Tests
Author.ts
Add Author entity with custom ID transformer                         

test/functional/columns/value-transformer/entity/Author.ts

  • Create new test entity with custom AuthorId class
  • Define AuthorId as custom object wrapper for string values
  • Add transformer to convert between AuthorId and string representation
+25/-0   
Article.ts
Add Article entity with foreign key transformer                   

test/functional/columns/value-transformer/entity/Article.ts

  • Create Article entity with custom ArticleId transformer
  • Add ManyToOne relationship to Author entity
  • Include authorId column with custom AuthorId transformer
+40/-0   
value-transformer.test.ts
Add test for foreign key transformer issue                             

test/functional/columns/value-transformer/value-transformer.test.ts

  • Import new Author and Article test entities
  • Register entities in test database connections
  • Add comprehensive test for foreign key column with custom object type
  • Verify transformed custom objects persist and load correctly
+39/-1   

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 24, 2026

commit: 99a40ce

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 24, 2026

PR Code Suggestions ✨

Latest suggestions up to 99a40ce

CategorySuggestion                                                                                                                                    Impact
Possible issue
Use safer plain-object detection

Replace the .constructor === Object check with a more robust prototype-based
check (Object.getPrototypeOf) to reliably detect plain objects and avoid
potential runtime errors with edge cases like Object.create(null).

src/metadata/ColumnMetadata.ts [834-837]

 !(embeddedObject[this.propertyName] instanceof Date) &&
 (!this.transformer ||
-    embeddedObject[this.propertyName].constructor ===
-        Object)
+    (() => {
+        const v = embeddedObject[this.propertyName]
+        const proto = Object.getPrototypeOf(v)
+        return proto === Object.prototype || proto === null
+    })())
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that using constructor === Object is not a robust way to check for plain objects and proposes a more reliable prototype-based check, improving the code's resilience to edge cases.

Low
Make object checks runtime-safe

Replace the .constructor === Object check with a safer, prototype-based check to
prevent unexpected failures or incorrect behavior when handling object values
during updates.

src/query-builder/UpdateQueryBuilder.ts [539-550]

 if (
     column.referencedColumn &&
     typeof value === "object" &&
     !(value instanceof Date) &&
     value !== null &&
     !Buffer.isBuffer(value) &&
     (!column.transformer ||
-        value.constructor === Object)
+        (() => {
+            const proto = Object.getPrototypeOf(value)
+            return proto === Object.prototype || proto === null
+        })())
 ) {
-    value =
-        column.referencedColumn.getEntityValue(value)
+    value = column.referencedColumn.getEntityValue(value)
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that using constructor === Object is not a robust way to check for plain objects and proposes a more reliable prototype-based check, improving the code's resilience to edge cases.

Low
  • More

Previous suggestions

Suggestions up to commit e1b97e4
CategorySuggestion                                                                                                                                    Impact
Possible issue
Refine logic for transformed foreign keys

Refine the logic for handling transformed foreign keys to allow foreign key
value extraction from a related entity object. The current implementation
incorrectly blocks this when a value transformer is present.

src/metadata/ColumnMetadata.ts [868-875]

 ObjectUtils.isObject(entity[this.propertyName]) &&
+(!this.transformer ||
+    (entity[this.propertyName] &&
+        entity[this.propertyName].constructor === Object)) &&
 !InstanceChecker.isFindOperator(
     entity[this.propertyName],
 ) &&
 !(typeof entity[this.propertyName] === "function") &&
 !Buffer.isBuffer(entity[this.propertyName]) &&
-!(entity[this.propertyName] instanceof Date) &&
-!this.transformer
+!(entity[this.propertyName] instanceof Date)
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that the PR's change, while fixing one issue, introduces a new bug by preventing foreign key extraction from a full entity object if the column has a transformer. The proposed fix is robust and handles both cases correctly.

High
Refine logic for embedded transformed FKs

Refine the logic for handling transformed foreign keys within embedded entities.
The current implementation incorrectly blocks foreign key extraction when a
value transformer is present on a column inside an embed.

src/metadata/ColumnMetadata.ts [833-835]

 !Buffer.isBuffer(embeddedObject[this.propertyName]) &&
 !(embeddedObject[this.propertyName] instanceof Date) &&
-!this.transformer
+(!this.transformer ||
+    (embeddedObject[this.propertyName] &&
+        embeddedObject[this.propertyName].constructor === Object))
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly applies the same critical logic fix from the first suggestion to the code path for embedded entities. This ensures consistent and correct behavior for transformed foreign keys within embeds.

High

@qodo-free-for-open-source-projects

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Update QB skips transformers 🐞 Bug ✓ Correctness
Description
The PR fixes FK value-object handling in ColumnMetadata.getEntityValue, but UpdateQueryBuilder still
extracts referencedColumn values from any object and then skips preparePersistentValue, so
transformed FK UPDATEs can still yield undefined/untransformed parameters.
Code

src/metadata/ColumnMetadata.ts[R874-875]

+                    !(entity[this.propertyName] instanceof Date) &&
+                    !this.transformer
Evidence
ColumnMetadata now avoids referencedColumn extraction when a transformer exists and then applies
transformer.to when requested, enabling value-object FK insert/load. UpdateQueryBuilder, however,
still forces referencedColumn.getEntityValue(value) for any object value and bypasses
driver.preparePersistentValue (which is where transformers are applied), so UPDATEs can still fail
for the same value-object FK scenario.

src/metadata/ColumnMetadata.ts[855-895]
src/query-builder/UpdateQueryBuilder.ts[537-554]
src/driver/postgres/PostgresDriver.ts[728-734]
test/functional/columns/value-transformer/value-transformer.test.ts[190-206]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`UpdateQueryBuilder` still treats any object value for a FK column (`column.referencedColumn`) as an entity/id-map and replaces it with `referencedColumn.getEntityValue(value)`, *skipping* `driver.preparePersistentValue(...)`. For transformed FK value objects (e.g., `AuthorId`), this can produce `undefined` (because the object lacks the referenced column property) and also bypass transformer application.

### Issue Context
This PR changed `ColumnMetadata.getEntityValue` to skip referenced id extraction when `this.transformer` is present, which makes inserting/loading FK value objects work. UPDATE query building is still inconsistent.

### Fix Focus Areas
- src/query-builder/UpdateQueryBuilder.ts[537-554]
- src/metadata/ColumnMetadata.ts[855-895]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Transformer guard too broad 🐞 Bug ✓ Correctness
Description
Gating referencedColumn extraction on !this.transformer fixes passing value objects directly (as
in the new test) but can break passing an entity/id-map object (e.g. { id: AuthorId(...) }) into a
transformed FK column, because the transformer will now receive the whole object instead of the
extracted id.
Code

src/metadata/ColumnMetadata.ts[R874-875]

+                    !(entity[this.propertyName] instanceof Date) &&
+                    !this.transformer
Evidence
ColumnMetadata’s FK-object handling previously extracted referenced ids from object values; the PR
now disables that path whenever a transformer exists. The codebase documents the pattern of
providing object values for relational columns and extracting the referenced id, so this change can
break that pattern specifically when a transformer is present (transformer.to gets an unexpected
object shape). The new test demonstrates the intended new-supported case (value object directly).

src/metadata/ColumnMetadata.ts[866-895]
src/query-builder/InsertQueryBuilder.ts[1537-1542]
test/functional/columns/value-transformer/value-transformer.test.ts[190-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new `!this.transformer` guard prevents referencedColumn extraction for **all** object inputs on FK columns when a transformer exists. This fixes value-object FK inputs (e.g., `authorId: new AuthorId(...)`) but can break existing patterns where the FK property is set to an entity or id-map object (e.g., `authorId: { id: new AuthorId(...) }`), because the transformer will receive the whole object rather than the extracted id.

### Issue Context
TypeORM has documented/implemented behavior to extract referenced ids from object inputs for relational columns (see InsertQueryBuilder comment). With the current change, that behavior is disabled whenever `transformer` is set.

### Fix Focus Areas
- src/metadata/ColumnMetadata.ts[814-842]
- src/metadata/ColumnMetadata.ts[855-883]
- src/query-builder/InsertQueryBuilder.ts[1537-1542]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Plain-object check inconsistent 🐞 Bug ⛯ Reliability ⭐ New
Description
The new value.constructor === Object plain-object check does not handle null-prototype objects
(e.g., Object.create(null)) and is inconsistent with existing repo logic, potentially changing FK
extraction behavior in edge cases.
Code

src/query-builder/UpdateQueryBuilder.ts[R541-547]

                            typeof value === "object" &&
                            !(value instanceof Date) &&
                            value !== null &&
-                            !Buffer.isBuffer(value)
+                            !Buffer.isBuffer(value) &&
+                            (!column.transformer ||
+                                value.constructor === Object)
                        ) {
Evidence
UpdateQueryBuilder adopts (!column.transformer || value.constructor === Object) which treats
Object.create(null) values as non-plain (constructor is undefined). The repo already has a
plain-object check that explicitly supports this case (`!item.constructor || item.constructor ===
Object`), indicating the new check is a weaker/inconsistent variant.

src/query-builder/UpdateQueryBuilder.ts[537-550]
src/util/OrmUtils.ts[540-547]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`value.constructor === Object` fails for null-prototype objects and is inconsistent with the repository’s existing plain-object logic.

### Issue Context
The repo already treats `Object.create(null)` as a plain object in `OrmUtils`.

### Fix Focus Areas
- src/query-builder/UpdateQueryBuilder.ts[537-550]
- src/metadata/ColumnMetadata.ts[826-838]
- src/metadata/ColumnMetadata.ts[869-879]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Update QB skips transformers 🐞 Bug ✓ Correctness
Description
The PR fixes FK value-object handling in ColumnMetadata.getEntityValue, but UpdateQueryBuilder still
extracts referencedColumn values from any object and then skips preparePersistentValue, so
transformed FK UPDATEs can still yield undefined/untransformed parameters.
Code

src/metadata/ColumnMetadata.ts[R874-875]

+                    !(entity[this.propertyName] instanceof Date) &&
+                    !this.transformer
Evidence
ColumnMetadata now avoids referencedColumn extraction when a transformer exists and then applies
transformer.to when requested, enabling value-object FK insert/load. UpdateQueryBuilder, however,
still forces referencedColumn.getEntityValue(value) for any object value and bypasses
driver.preparePersistentValue (which is where transformers are applied), so UPDATEs can still fail
for the same value-object FK scenario.

src/metadata/ColumnMetadata.ts[855-895]
src/query-builder/UpdateQueryBuilder.ts[537-554]
src/driver/postgres/PostgresDriver.ts[728-734]
test/functional/columns/value-transformer/value-transformer.test.ts[190-206]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`UpdateQueryBuilder` still treats any object value for a FK column (`column.referencedColumn`) as an entity/id-map and replaces it with `referencedColumn.getEntityValue(value)`, *skipping* `driver.preparePersistentValue(...)`. For transformed FK value objects (e.g., `AuthorId`), this can produce `undefined` (because the object lacks the referenced column property) and also bypass transformer application.
### Issue Context
This PR changed `ColumnMetadata.getEntityValue` to skip referenced id extraction when `this.transformer` is present, which makes inserting/loading FK value objects work. UPDATE query building is still inconsistent.
### Fix Focus Areas
- src/query-builder/UpdateQueryBuilder.ts[537-554]
- src/metadata/ColumnMetadata.ts[855-895]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Transformer guard too broad 🐞 Bug ✓ Correctness
Description
Gating referencedColumn extraction on !this.transformer fixes passing value objects directly (as
in the new test) but can break passing an entity/id-map object (e.g. { id: AuthorId(...) }) into a
transformed FK column, because the transformer will now receive the whole object instead of the
extracted id.
Code

src/metadata/ColumnMetadata.ts[R874-875]

+                    !(entity[this.propertyName] instanceof Date) &&
+                    !this.transformer
Evidence
ColumnMetadata’s FK-object handling previously extracted referenced ids from object values; the PR
now disables that path whenever a transformer exists. The codebase documents the pattern of
providing object values for relational columns and extracting the referenced id, so this change can
break that pattern specifically when a transformer is present (transformer.to gets an unexpected
object shape). The new test demonstrates the intended new-supported case (value object directly).

src/metadata/ColumnMetadata.ts[866-895]
src/query-builder/InsertQueryBuilder.ts[1537-1542]
test/functional/columns/value-transformer/value-transformer.test.ts[190-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `!this.transformer` guard prevents referencedColumn extraction for **all** object inputs on FK columns when a transformer exists. This fixes value-object FK inputs (e.g., `authorId: new AuthorId(...)`) but can break existing patterns where the FK property is set to an entity or id-map object (e.g., `authorId: { id: new AuthorId(...) }`), because the transformer will receive the whole object rather than the extracted id.
### Issue Context
TypeORM has documented/implemented behavior to extract referenced ids from object inputs for relational columns (see InsertQueryBuilder comment). With the current change, that behavior is disabled whenever `transformer` is set.
### Fix Focus Areas
- src/metadata/ColumnMetadata.ts[814-842]
- src/metadata/ColumnMetadata.ts[855-883]
- src/query-builder/InsertQueryBuilder.ts[1537-1542]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coveralls
Copy link

Coverage Status

coverage: 81.416% (-0.003%) from 81.419%
when pulling 99a40ce on gioboa:fix/9565
into f47246c on typeorm:master.

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.

Foreign key property transformation processes undefined value upon save

2 participants