Skip to content

Required the custom fields services on their members consumers - #29485

Merged
rob-ghost merged 2 commits into
mainfrom
chore/require-custom-fields-service-dependency
Jul 22, 2026
Merged

Required the custom fields services on their members consumers#29485
rob-ghost merged 2 commits into
mainfrom
chore/require-custom-fields-service-dependency

Conversation

@rob-ghost

@rob-ghost rob-ghost commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

The custom fields services are exported as module-level bindings that are undefined until init() runs. Their consumers in the members service each invented a way to cope with that: the member create path checked whether the request body carried values before asking the service anything, and the CSV exporter warned and exported without custom field columns when the definitions service was absent.

Neither state can occur. Boot builds the custom fields services before the members service, so by the time either consumer exists the dependency is resolved. Both were guarding against something the dependency graph already rules out, and the guards obscured that.

Solution

Both consumers now receive the services they need rather than reaching for the module and hoping. The members service is handed the values service at construction, where boot order is expressed; the exporter is a factory built during the members service's init, alongside the other members that are resolved there rather than required statically. With the dependency genuinely present, the guards go, and the labs flag is left as the only thing deciding whether custom fields participate.

Fixtures now build these services the way boot does. One of them was constructing a members service that could not exist in a running Ghost, which is what made the guard look necessary in the first place. The hand-written type describing the values service is replaced by the real exported type, which cannot drift from it.

The Admin API endpoint still unwraps the definitions service with a non-null assertion. Endpoint modules are constructed at import time and cannot take an injected instance without a much larger change, so that one is left as a known case.

No request or export changes behaviour.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

Walkthrough

CustomFieldValuesService.namesValues now returns false for undefined input while continuing to validate defined inputs. Member services receive customFieldValues directly for reading, planning, and applying custom-field values. The members exporter now receives custom-field definitions and values through initialization-time dependency injection. Tests add validation coverage and centralize custom-field value stubs.

Possibly related PRs

Suggested reviewers: 9larsons

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is clearly related to the main change: making custom fields services required for members consumers.
Description check ✅ Passed The description matches the changeset and accurately explains the dependency and exporter wiring updates.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/require-custom-fields-service-dependency

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit d7aec80

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 3m 6s View ↗
nx run ghost:test:integration ✅ Succeeded 3m 4s View ↗
nx run ghost:test:legacy ✅ Succeeded 3m 1s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 32s View ↗
nx run ghost-monorepo:lint:boundaries ✅ Succeeded 16s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 32s View ↗
nx run-many -t lint -p ghost,ghost-monorepo ✅ Succeeded 17s View ↗
nx run @tryghost/admin:build ✅ Succeeded 10s View ↗
nx run-many --target=build --projects=tag:publi... ✅ Succeeded <1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-21 11:35:04 UTC

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.55%. Comparing base (0adc3a5) to head (d7aec80).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #29485      +/-   ##
==========================================
- Coverage   74.60%   74.55%   -0.05%     
==========================================
  Files        1604     1604              
  Lines      140867   140910      +43     
  Branches    17179    17194      +15     
==========================================
- Hits       105096   105060      -36     
- Misses      34716    34819     +103     
+ Partials     1055     1031      -24     
Flag Coverage Δ
e2e-tests 76.63% <100.00%> (-0.06%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Base automatically changed from chore/tidy-member-custom-fields-comments to main July 21, 2026 10:03
@rob-ghost
rob-ghost force-pushed the chore/require-custom-fields-service-dependency branch 2 times, most recently from 5a584a3 to d814cae Compare July 21, 2026 10:28
MemberBREADService's add() guarded its call into the custom fields service on
the request body carrying a custom_fields key. The custom fields service is a
required dependency of the members service, and boot constructs it before the
members service, so an instance without one cannot exist in a running Ghost
and the guard was protecting a state that cannot occur. The dependency is now
also inverted: the members service previously received the custom fields
module namespace and reached through it as customFieldsService.values on
every call, which meant depending on a module-level binding that is undefined
until init() runs, so it takes the values service itself as customFieldValues,
resolved once in services/members/api.js at construction. Custom fields is
initialised in initCore and the members API is built in initServices, so
construction is where boot order is expressed rather than each call site
late-binding through a namespace. The hand-written typedef restating the
service's shape is replaced by a reference to the real exported
CustomFieldValuesService type, matching how memberRepository and offersAPI are
already typed in that file, because a restated structural typedef can drift
from the service it describes and an imported one cannot. The unit fixtures
previously built a service that could not exist in production, and now build
it the way boot does, sharing a single stub so the factories in that file
cannot drift apart. Nothing enforces the dependency at runtime, because a
dependency that cannot be absent does not need a guard; making it non-optional
in the type system belongs with converting the file to TypeScript. No
behaviour change for any request.
@rob-ghost
rob-ghost force-pushed the chore/require-custom-fields-service-dependency branch from d814cae to 11023d4 Compare July 21, 2026 10:34
@rob-ghost rob-ghost changed the title Required a custom fields service on MemberBREADService Required the custom fields services on their members consumers Jul 21, 2026
Boot builds the custom fields services before the members service, so the
exporter cannot observe them as absent. The module-level require plus the
warn-and-degrade branch guarded a state that cannot occur, so the exporter is
now a factory taking the definitions and values services, constructed in the
members service's init() alongside the other late-resolved members. The labs
flag alone decides whether custom field columns are included, which is what it
was always meant to decide. The Admin API endpoint still unwraps definitions
with a non-null assertion, because endpoint modules are constructed at import
time and cannot take an injected instance without a much larger change. No
behaviour change either way on the flag.
@rob-ghost
rob-ghost force-pushed the chore/require-custom-fields-service-dependency branch from f5cb166 to d7aec80 Compare July 21, 2026 11:23
@rob-ghost
rob-ghost merged commit dc99bb2 into main Jul 22, 2026
49 checks passed
@rob-ghost
rob-ghost deleted the chore/require-custom-fields-service-dependency branch July 22, 2026 10:06
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