Adding a Service
Each microservice in this repo is self-contained: own package.json, own
DB schema, own createMicroservice boot. This is the checklist every new
service follows — nothing more, nothing less.
1. Package
Code
capabilities[]is whatServiceRegistrarpublishes — the BE registry and the AI docs-pack pull both read it.version:auto/prebuildcall../scripts/version-sync.mjs(root script) to keep versions aligned across services.
2. Boot — src/index.ts
One createMicroservice(options) call. Required options:
serviceName, envSchema (closed-set: exactly DATABASE_URL +
DATABASE_SCHEMA — assertDbEnv() parses DATABASE_SCHEMA into the
string[] domain; [0] is the service's owned schema and is sent to the
BE as the service.register schema claim), initDal/dalClose,
configRepositoryAdapter, authConfigPort, healthCheckPort,
routeHandler (factory-produced only).
Domain wiring goes in onReady(ctx) — NATS subscribers, config holder
init, cron jobs. See SDK's microservice-anatomy for the full 12-stage
boot sequence.
3. Database — db-meta/
Code
Seed at minimum: http_port, service_base_url,
service_client_shield_secret (type secret + _header companion),
nats_url, plus the service's own config keys — all via a numbered patch.
4. Routes + Spec — src/service-routes.ts
Declare the service's whole HTTP surface ONCE via makeServiceRoutes —
it derives BOTH the request routers and /api/v1/openapi.json from the
same declaration, so the spec can never drift from the registered routes:
Code
Pass the result straight to createMicroservice({ routeHandler }) — it
is factory-branded. The handler carries .spec (the built OpenAPI doc):
scripts/build-docs-pack.ts embeds myServiceRouteHandler.spec. A route
that exists but isn't documented (or vice versa) is impossible by
construction — never hand-compose buildOpenApiSpec/entityCrudSpec/
makeOpenApiHandler separately.
6. Docs pack — scripts/build-docs-pack.ts
Copy the standard script verbatim, changing repo, docsDir (omit for
dev-only services) and openapi. Build it in the deploy chain —
ServiceRegistrar auto-advertises docs_pack when dist/docs-pack/ manifest.json exists.
7. Registration — automatic
On boot the service publishes service.register; the BE registry learns
name, base_url, capabilities, endpoints — and the /ws/<code>/* proxy
path becomes reachable. Nav/sidebar entries for its screens are declared
by the service itself in the endpoints.nav block of the
service.register payload (route_prefixes + nav[] + full routes
census — validated at enrollment; the BE's static module-nav-meta
registry is reserved for BE-owned modules like settings).
Root level: the service declares partOf in its registration options
(service_registry.part_of). Its single FE route namespace is derived,
never free-chosen: /{part_of}/{code} — e.g. partOf: "system" + code
job → all nav hrefs and census routes under /system/job/**. When the
service IS its own root (partOf === code, e.g. crm) the namespace
collapses to /{code} → /crm/**. A mismatch is rejected at register
time.
Display name: declare labelKey (→ service_registry.label_key) —
the i18n key for the module's localized name, resolved against the
service's OWN *.translations schema (seed it there, like nav[].label_key
entries). Every UI surface (app launcher, breadcrumbs, module menus)
renders it via resolveModuleLabel with the raw name as fallback.
8. Docs
<svc>/docs/dev-guide/index.mdx — always. <svc>/docs/user-guide/ only
if the feature is end-user visible. Update docs/dev-guide/overview.mdx's
service catalog.