Skip to content

Latest commit

 

History

History
146 lines (116 loc) · 7.83 KB

File metadata and controls

146 lines (116 loc) · 7.83 KB

Ghost Core services

Services under this directory own domain and integration logic used by Ghost Core through application-owned instances. The directory is a catalogue of runtime services, not a general home for all server-side business logic or I/O. Use the definition below when adding or relocating a top-level entry; existing placement alone is not evidence that something belongs here.

What belongs here

A service root is the application entry point for a capability whose instance, dependencies, and availability are owned by Ghost's boot lifecycle. It wires an implementation for callers to use, rather than merely exporting a class for callers to construct themselves.

This includes both:

  • Resource-owning services, which need lifecycle coordination for work such as event subscriptions, scheduled jobs, or resources requiring cleanup.
  • Composition-only services, which assemble dependencies and expose an application-owned instance without needing background work or a stop hook.

Having a timer or a shutdown method is not a prerequisite. Equally, performing I/O, reading configuration, containing business rules, or having a large implementation does not by itself make a module a service root. Do not add an empty lifecycle wrapper just to justify a directory's location.

These rules apply to top-level entries, not every file inside a service. A service can own ordinary classes, repositories, functions, types, templates, and provider adapters as private implementation details.

Supporting code and other homes

  • Keep implementation used only by one service inside that service.
  • Put shared server libraries, reusable constructors, and provider clients in server/lib/, grouped by a specific capability.
  • Put HTTP routing and middleware in the appropriate part of server/web/.
  • Keep API-specific support with its API owner rather than making it a service.

For example:

  • email-service exposes an application-owned wrapper initialized during boot.
  • email-address constructs and exposes the application's email-address implementation through init() and service. It qualifies without owning a background worker.
  • Magic-link token and URL helpers are reusable support for several workflows, not a separately boot-managed capability. They belong in their own library, not a mail service merely because some callers send the links by email.

Relocation does not resolve resource ownership by itself. If a supporting client acquires resources, its owner must still arrange any necessary cleanup.

Implementation and migration

The definition above guides new roots and directory cleanup. Existing roots still use different construction and export patterns; a uniform managed lifecycle interface is not yet the implemented contract throughout this directory. Do not assume every root exports init, service, and shutdown. Standardizing that interface must include the implementation, boot wiring, and tests, not just a documentation change.

The temporary service inventory tracks this migration. Update it when adding, removing or renaming a root. It includes standalone files and supporting modules still awaiting relocation. Boot never reads it.

Each entry records its intended ownership and audit status. pending means the contract audit has not started; auditing means evidence is incomplete; audited means all facets have been investigated and recorded from source or test evidence. It describes audit completeness, not test coverage or migration readiness. An audited entry can still have test gaps or known bugs in blockers. The notes describe audit priorities and intended changes, not verified contracts.

auditedAt is the full source commit SHA the record was last checked against. Add it when completing or refreshing an audit; older records may omit it. Advance it only after reviewing relevant changes, not merely rebasing. Recorded contracts describe that revision. Preserve them and recheck relevant changes when work on that service begins. If cited evidence has since been deleted, keep a link to the file at the audited revision in the summary; sources and tests list files that still exist, without implying freshness.

Before relying on a record, compare its service directory and cited source/test paths with that revision, and check for new callers. Changed files flag a recheck; the reference does not prove tests ran at that revision. Inventory validation checks its format without requiring Git history.

The unverified facets are open questions:

Facet Question to resolve
exports What runtime exports and import conventions do callers use?
construction Where are instances constructed, including deep imports?
consumers Which application, request, job or CLI callers use them?
startup What must be ready before each supported use?
lifetime What identity and state survive initialization or restart?
configuration Which values stay live and which are captured?
resources Who owns, drains and releases resources, including nested ones?
failures Which failures abort, continue, retry or disable the capability?

Recorded summaries link to source and test evidence using paths relative to ghost. Entry points are relative to this services directory and include separate composition paths reached through deep imports. They are not a list of every importable helper, type or job definition. An empty test list means the summary has source evidence only, unless it links historical tests. A nonempty list does not imply that every claim is tested: when coverage is partial, the summary must say what the cited tests assert and which claims are source-only. Keep established source facts under contracts; reserve unverified for questions the audit has not answered.

Record missing coverage and known bugs in blockers. Before changing a service, address the gaps relevant to that change; neither audited nor a passing inventory check proves that its behavior is protected against regressions.

The unit test checks the inventory's shape, paths and directory coverage. It does not import services or drive initialization. README.md and the inventory itself are the only metadata exclusions. Retire the inventory after migration; keep the behavioral tests and replace the legacy check with a check for the established service convention.

Follow an existing service in the same area when extending established code, without treating its legacy placement or lifecycle shortcuts as requirements. New standalone service logic should be TypeScript unless it must extend an existing JavaScript module.

The gifts, donations, and related services show the current transition pattern: domain logic uses TypeScript with named exports, while a thin CommonJS index.js or wrapper remains only where boot code or an existing require() boundary needs it.

Initialization

Ghost's boot sequence owns service construction. A new service that requires initialization must expose an explicit init() and be called from ghost/core/boot.js in the appropriate boot phase. Do not make the first request responsible for constructing the service.

Keep wrapper initialization idempotent when callers may safely reach it more than once. Add shutdown or cleanup handling to the boot lifecycle when the service owns resources that must be released.

Related guidance