Skip to content

[upstream #4965] feat(core): Auto-initialise customFields for entities that support them - #22

Open
ayim wants to merge 30 commits into
masterfrom
mirror/upstream-4965
Open

[upstream #4965] feat(core): Auto-initialise customFields for entities that support them#22
ayim wants to merge 30 commits into
masterfrom
mirror/upstream-4965

Conversation

@ayim

@ayim ayim commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Mirrored from vendurehq#4965 for the Overwatch review demo.

Original author: @grolmus


Summary

Auto-initialises an empty customFields array for every entity that supports custom fields, so a plugin's configuration callback can extend any such entity (core or plugin-defined) without the defensive guard:

// Before — guard required, or startup crashes with "Cannot read properties of undefined"
configuration: config => {
    if (!config.customFields.CompanyRole) (config.customFields as any).CompanyRole = [];
    config.customFields.CompanyRole.push({ name: 'spendingLimit', type: 'int' });
}

// After — just works
configuration: config => {
    config.customFields.CompanyRole.push({ name: 'spendingLimit', type: 'int' });
}

Resolves OSS-408.

How

  • New getEntityNamesWithCustomFields() (register-custom-entity-fields.ts) detects custom-field-capable entities from the TypeORM metadata (they declare a customFields embedded column) — a HasCustomFields implements check isn't available at runtime.
  • runPluginConfigurations() (bootstrap.ts) pre-seeds config.customFields[EntityName] = [] for each of those before the plugin configuration callbacks run, only where an entry doesn't already exist (never overwrites configured fields).

Empty arrays are ignored by registerCustomEntityFields (it already skips zero-length configs), so this is inert for entities nobody extends — no schema/column changes.

The TypeScript half of the original issue (the as any footgun) is already resolved on main: CustomFields now carries a & { [entity: string]: CustomFieldConfig[] } index signature, so config.customFields.CompanyRole is typed without a cast. This PR adds the matching runtime guarantee.

Tests

bootstrap.spec.ts — custom-field-capable entities get [] initialised; existing entries are preserved; and a @VendurePlugin({ configuration }) can push to another entity's custom fields without a guard. Source typechecks clean; related entity specs pass.

Relates to OSS-408


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

vendure-ci-automation-bot Bot and others added 30 commits July 10, 2026 07:53
…o-initialise-configcustomfields-for-plugin-entities
`getEntityNamesWithCustomFields()` called every `translations` relation target
as a function to read its `.name`. TypeORM also allows a bare string target and
a closure returning a string (both used to break circular imports, e.g.
`@OneToMany('ArticleTranslation', ...)`). The former threw
`relation.type is not a function` — and since this runs unconditionally at the
top of `runPluginConfigurations()`, one such relation anywhere in the process
aborted every bootstrap; the latter yielded `undefined` and silently failed to
exclude the translation entity, re-introducing the duplicate-`customFields`
schema error this exclusion exists to prevent.

Added `getRelationTargetName()` which resolves all three shapes to the entity
name, guarding `typeof type === 'function'` the same way `getEntityTranslation`
in validate-custom-fields-config.ts does. Tests cover string, closure-returning-
string, and constructor-closure targets.

Relates to vendurehq#4965
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.

3 participants