# `build-routes` (campaigns) ## Purpose `buildCampaignRoutes` is the single aggregator that materialises every `/campaigns/*` `PluginRouteEntry` in a canonical order. The plugin's `onInit` consumes the array verbatim — there is no per-route opt-in or configuration knob — so the OpenAPI spec generated for the campaigns tree is deterministic across deployments and across reloads. Tests assert the canonical order so accidental reordering during refactors surfaces immediately rather than as silent spec drift. The aggregator is intentionally a flat function returning a readonly array (rather than a class or builder). The cost of describing 12 routes inline is small, the structure is obvious, and downstream consumers can `array.map(toPrefixed)` cheaply when they need to mount under a non-root prefix. ## Features - Returns 12 entries in a documented canonical order. - Pure: no I/O, no side effects, no captured registry. - Each entry exposes `route: RouteConfig` + `handler: RouteHandler` so it can be mounted either via `definePluginRoutes` (production) or directly on a bare `Hono` app (tests). ## Usage: Basic ```ts import { buildCampaignRoutes } from "@suaveplan/api-plugin-campaigns"; const entries = buildCampaignRoutes(); for (const e of entries) { app[e.route.method as "get" | "post" | "patch" | "delete"]( e.route.path, (c) => e.handler(c) ); } ``` ## Usage: Advanced ```ts import { buildCampaignRoutes, definePluginRoutes, } from "@suaveplan/api-plugin-campaigns"; const prefix = "/api/v1"; const prefixed = buildCampaignRoutes().map((e) => ({ ...e, route: { ...e.route, path: `${prefix}${e.route.path}` }, })); definePluginRoutes(prefixed, { app, registry, pluginName: "campaigns" }); ``` ## API Reference - `buildCampaignRoutes(): readonly PluginRouteEntry[]` — returns the 12 canonical campaign route entries. ## Implementation Notes The canonical order — list, create, get, update, delete, send, pause, cancel, duplicate, test, stats, events — places the read endpoints before write/action endpoints (so OpenAPI consumers naturally see the read surface first) and groups the lifecycle action routes together. The `events` route comes last because it depends on the `deliveryEventStore` slot being populated, which is the only non-trivial dependency in the bundle; co-locating it with `stats` makes the dependency footprint visible at a glance.