# @suaveplan/api-plugin-site - Multi-site / multi-domain management plugin for @suaveplan/api. Stores `site` records with name / slug / theme / settings / push config, resolves Host headers to a site through DomainResolverService, exposes public + private OpenAPI route trees, ships an injectable domain-resolver Hono middleware, emits site:settings:updated events, optionally auto-creates a default site on organization:created. 14 SITE.* error codes, 5 OTel instruments, owner-scoped queries, server-tier. Multi-site / multi-domain management plugin for `@suaveplan/api`. Stores `site` records (name, slug, theme, settings, push config) in SurrealDB, resolves incoming `Host` headers to a site via `DomainResolverService`, exposes public + private OpenAPI Hono route trees, and emits the `site:settings:updated` event whenever a site's settings change so downstream caches / CDNs can react. ## Documentation tree - [error-codes](./src/error-codes/error-codes.md) — Owns the stable `SITE.*` error-code registry and the `SiteError` class used by every public API path in `@suaveplan/api… - [error-codes](./src/error-codes/error-codes.md) — Owns the stable `SITE.*` error-code registry and the `SiteError` class used by every public API path in `@suaveplan/api… - [integration](./src/integration/middleware-recommendations.md) — `MIDDLEWARE_RECOMMENDATIONS` is a frozen, typed bundle of recommended rate-limit and audit configuration for each docum… - [middleware-recommendations](./src/integration/middleware-recommendations.md) — `MIDDLEWARE_RECOMMENDATIONS` is a frozen, typed bundle of recommended rate-limit and audit configuration for each docum… - [middleware](./src/middleware/domain-resolver.md) — Factory that builds a Hono middleware which resolves the incoming request's `Host` header to a `site` + `organization`… - [domain-resolver](./src/middleware/domain-resolver.md) — Factory that builds a Hono middleware which resolves the incoming request's `Host` header to a `site` + `organization`… - [otel](./src/otel/instruments.md) — Owns the lazy-built OpenTelemetry instrument bundle (`SiteInstruments`) that every observable code path in `@suaveplan/… - [instruments](./src/otel/instruments.md) — Owns the lazy-built OpenTelemetry instrument bundle (`SiteInstruments`) that every observable code path in `@suaveplan/… - [plugin](./src/plugin/plugin.md) — Owns `createSitePlugin`, the canonical factory that produces the `PluginDescriptor` consumed by `@suaveplan/api-plugin`… - [plugin](./src/plugin/plugin.md) — Owns `createSitePlugin`, the canonical factory that produces the `PluginDescriptor` consumed by `@suaveplan/api-plugin`… - [private-routes](./src/routes/private-routes.md) — Builds the authenticated Hono route tree for site CRUD and domain management. The factory is intentionally auth-agnosti… - [public-routes](./src/routes/public-routes.md) — Builds the public (no-auth) Hono route tree that the plugin mounts under the configured `routePrefix`. The two endpoint… - [domain](./src/schemas/domain.md) — Zod schemas for every domain-shaped value: the validated domain literal itself, the request/response shapes for `addDom… - [options](./src/schemas/options.md) — Owns the plugin-options schema and the `mergeConfig` / `DEFAULT_CONFIG` helpers used by `createSitePlugin(...)`. Every… - [push](./src/schemas/push.md) — Schemas and a pure validator for the per-site push-notification configuration. The site plugin treats push config as op… - [site](./src/schemas/site.md) — Defines the Zod schemas that describe the canonical `site` entity, its create/update inputs, the public-facing slice, a… - [db-port](./src/service/db-port.md) — Declares `SiteDb`, the structural database port that `SiteService` and `DomainResolverService` consume. suaveplan's found… - [domain-resolver](./src/service/domain-resolver.md) — `DomainResolverService` maps a request's `Host` (or arbitrary domain string) to a persisted `site` record and the ownin… - [normalize-domain](./src/service/normalize-domain.md) — Pure helper that mirrors the SurrealDB `fn::site::domain::normalize` function in TypeScript so application-tier code (t… - [site](./src/service/site.md) — `SiteService` is the application-tier owner of every CRUD path against the SurrealDB `site` table. It mediates between… - [services](./src/services/registry-augmentation.md) — Augments the `ServiceRegistry` interface from `@suaveplan/api-plugin` so the three slots the site plugin owns — `siteSe… - [registry-augmentation](./src/services/registry-augmentation.md) — Augments the `ServiceRegistry` interface from `@suaveplan/api-plugin` so the three slots the site plugin owns — `siteSe… - [types](./src/types/types.md) — Runtime-free type module that defines every shared shape used by the site plugin: persisted `Site` / `SiteSettings` / `… - [types](./src/types/types.md) — Runtime-free type module that defines every shared shape used by the site plugin: persisted `Site` / `SiteSettings` / `… ## Install ```bash bun add @suaveplan/api-plugin-site ``` ## Quick start ```ts import { createPluginRegistry } from "@suaveplan/api-plugin"; import { createDatabasePlugin } from "@suaveplan/api-plugin-database"; import { createSitePlugin } from "@suaveplan/api-plugin-site"; const registry = createPluginRegistry(); registry.register(createDatabasePlugin({ /* surrealdb config */ })); registry.register(createSitePlugin({ maxSitesPerOrg: 10, maxDomainsPerSite: 5, autoCreateDefaultSite: true, defaultDomainPattern: "{slug}.suaveplan.app", })); await registry.start(); ``` After `registry.start()`, the site plugin exposes three services on `ServiceRegistry`: - `siteService: SiteService` — CRUD + settings + domain management. - `domainResolver: DomainResolverService` — `resolve(domain)` returning `{success, site, organization}`. - `domainResolverMiddleware: Hono Middleware` — request-scoped resolver that injects `site` / `organization` / `siteId` / `organizationId` into `c`. ## What it does - Persists `site` records under SurrealDB with per-table SurQL migration shipped in `surql/tables/site.surql`. - Enforces per-organization and per-site caps (`maxSitesPerOrg`, `maxDomainsPerSite`). - Public route: `GET /sites/resolve/{domain}` — domain → site/org lookup. - Public route: `GET /sites/{id}/public` — sanitised `SitePublicInfo` projection (no push secrets). - Private routes: `POST /sites`, `PATCH /sites/{id}`, `POST /sites/{id}/domains`, `DELETE /sites/{id}/domains`. - Optional `organization:created` listener that auto-creates a default site per new organisation, with `site:bootstrap:failed` events on failure. - 14 stable `SITE.*` error codes raised through `SiteError extends SuaveplanError`. - OTel instruments: `site.sites_created_total{outcome}`, `site.sites_updated_total{kind}`, `site.domain_resolutions_total{outcome}`, `site.domain_resolution_duration_ms`, `site.bootstrap_failed_total`. ## Modules - [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — `SITE_CODES` + `SiteError`. - [`src/otel/instruments.md`](./src/otel/instruments.md) — OTel instrument bundle. - [`src/schemas/site.md`](./src/schemas/site.md) — `SiteSchema`, `CreateSiteSchema`, `UpdateSiteSchema`, `SitePublicInfoSchema`. - [`src/schemas/domain.md`](./src/schemas/domain.md) — `DomainSchema`, resolve / add / remove request+response. - [`src/schemas/push.md`](./src/schemas/push.md) — `ApplePushConfigSchema`, `FirebasePushConfigSchema`, `validatePushConfig`. - [`src/schemas/options.md`](./src/schemas/options.md) — `SitePluginOptionsSchema`, `mergeConfig`, `DEFAULT_CONFIG`. - [`src/service/site.md`](./src/service/site.md) — `SiteService`. - [`src/service/domain-resolver.md`](./src/service/domain-resolver.md) — `DomainResolverService`. - [`src/service/db-port.md`](./src/service/db-port.md) — `SiteDb` structural port. - [`src/service/normalize-domain.md`](./src/service/normalize-domain.md) — domain canonicalisation. - [`src/middleware/domain-resolver.md`](./src/middleware/domain-resolver.md) — Hono middleware factory. - [`src/routes/public-routes.md`](./src/routes/public-routes.md) — public OpenAPI router. - [`src/routes/private-routes.md`](./src/routes/private-routes.md) — private OpenAPI router (auth supplied by caller). - [`src/plugin/plugin.md`](./src/plugin/plugin.md) — `createSitePlugin` factory. - [`src/services/registry-augmentation.md`](./src/services/registry-augmentation.md) — `ServiceRegistry` module augmentation. - [`src/integration/middleware-recommendations.md`](./src/integration/middleware-recommendations.md) — `MIDDLEWARE_RECOMMENDATIONS` constant + types. ## Middleware composition The site plugin ships **zero runtime dependencies** on `@suaveplan/api-plugin-rate-limit` and `@suaveplan/api-plugin-audit`. Composition stays in the host application via Hono's `app.use(prefix, middleware)` pattern. - See [`docs/middleware-integration.md`](./docs/middleware-integration.md) for the canonical end-to-end wiring example and the recommended layering order (audit OUTERMOST → rate-limit MIDDLE → site routes INNERMOST). - See [`src/integration/middleware-recommendations.md`](./src/integration/middleware-recommendations.md) for the `MIDDLEWARE_RECOMMENDATIONS` constant exposing recommended rate-limit windows and audit categories per route group. - The pattern is proven by [`src/integration/middleware-composition.integration.test.ts`](./src/integration/middleware-composition.integration.test.ts) — a runnable composition asserting site behaviour, rate-limit headers, and audit event emission.