@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 — Owns the stable
SITE.*error-code registry and theSiteErrorclass used by every public API path in `@suaveplan/api…- error-codes — Owns the stable
SITE.*error-code registry and theSiteErrorclass used by every public API path in `@suaveplan/api…
- error-codes — Owns the stable
- integration —
MIDDLEWARE_RECOMMENDATIONSis a frozen, typed bundle of recommended rate-limit and audit configuration for each docum…- middleware-recommendations —
MIDDLEWARE_RECOMMENDATIONSis a frozen, typed bundle of recommended rate-limit and audit configuration for each docum…
- middleware-recommendations —
- middleware — Factory that builds a Hono middleware which resolves the incoming request's
Hostheader to asite+organization…- domain-resolver — Factory that builds a Hono middleware which resolves the incoming request's
Hostheader to asite+organization…
- domain-resolver — Factory that builds a Hono middleware which resolves the incoming request's
- otel — Owns the lazy-built OpenTelemetry instrument bundle (
SiteInstruments) that every observable code path in `@suaveplan/…- instruments — Owns the lazy-built OpenTelemetry instrument bundle (
SiteInstruments) that every observable code path in `@suaveplan/…
- instruments — Owns the lazy-built OpenTelemetry instrument bundle (
- plugin — Owns
createSitePlugin, the canonical factory that produces thePluginDescriptorconsumed by@suaveplan/api-plugin…- plugin — Owns
createSitePlugin, the canonical factory that produces thePluginDescriptorconsumed by@suaveplan/api-plugin…
- plugin — Owns
- private-routes — Builds the authenticated Hono route tree for site CRUD and domain management. The factory is intentionally auth-agnosti…
- public-routes — Builds the public (no-auth) Hono route tree that the plugin mounts under the configured
routePrefix. The two endpoint… - domain — Zod schemas for every domain-shaped value: the validated domain literal itself, the request/response shapes for `addDom…
- options — Owns the plugin-options schema and the
mergeConfig/DEFAULT_CONFIGhelpers used bycreateSitePlugin(...). Every… - push — Schemas and a pure validator for the per-site push-notification configuration. The site plugin treats push config as op…
- site — Defines the Zod schemas that describe the canonical
siteentity, its create/update inputs, the public-facing slice, a… - db-port — Declares
SiteDb, the structural database port thatSiteServiceandDomainResolverServiceconsume. suaveplan's found… - domain-resolver —
DomainResolverServicemaps a request'sHost(or arbitrary domain string) to a persistedsiterecord and the ownin… - normalize-domain — Pure helper that mirrors the SurrealDB
fn::site::domain::normalizefunction in TypeScript so application-tier code (t… - site —
SiteServiceis the application-tier owner of every CRUD path against the SurrealDBsitetable. It mediates between… - services — Augments the
ServiceRegistryinterface from@suaveplan/api-pluginso the three slots the site plugin owns — `siteSe…- registry-augmentation — Augments the
ServiceRegistryinterface from@suaveplan/api-pluginso the three slots the site plugin owns — `siteSe…
- registry-augmentation — Augments the
- types — Runtime-free type module that defines every shared shape used by the site plugin: persisted
Site/SiteSettings/ `…- types — Runtime-free type module that defines every shared shape used by the site plugin: persisted
Site/SiteSettings/ `…
- types — Runtime-free type module that defines every shared shape used by the site plugin: persisted
Install
bun add @suaveplan/api-plugin-site
Quick start
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 injectssite/organization/siteId/organizationIdintoc.
What it does
- Persists
siterecords under SurrealDB with per-table SurQL migration shipped insurql/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— sanitisedSitePublicInfoprojection (no push secrets). - Private routes:
POST /sites,PATCH /sites/{id},POST /sites/{id}/domains,DELETE /sites/{id}/domains. - Optional
organization:createdlistener that auto-creates a default site per new organisation, withsite:bootstrap:failedevents on failure. - 14 stable
SITE.*error codes raised throughSiteError 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—SITE_CODES+SiteError.src/otel/instruments.md— OTel instrument bundle.src/schemas/site.md—SiteSchema,CreateSiteSchema,UpdateSiteSchema,SitePublicInfoSchema.src/schemas/domain.md—DomainSchema, resolve / add / remove request+response.src/schemas/push.md—ApplePushConfigSchema,FirebasePushConfigSchema,validatePushConfig.src/schemas/options.md—SitePluginOptionsSchema,mergeConfig,DEFAULT_CONFIG.src/service/site.md—SiteService.src/service/domain-resolver.md—DomainResolverService.src/service/db-port.md—SiteDbstructural port.src/service/normalize-domain.md— domain canonicalisation.src/middleware/domain-resolver.md— Hono middleware factory.src/routes/public-routes.md— public OpenAPI router.src/routes/private-routes.md— private OpenAPI router (auth supplied by caller).src/plugin/plugin.md—createSitePluginfactory.src/services/registry-augmentation.md—ServiceRegistrymodule augmentation.src/integration/middleware-recommendations.md—MIDDLEWARE_RECOMMENDATIONSconstant + 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.mdfor 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.mdfor theMIDDLEWARE_RECOMMENDATIONSconstant exposing recommended rate-limit windows and audit categories per route group. - The pattern is proven by
src/integration/middleware-composition.integration.test.ts— a runnable composition asserting site behaviour, rate-limit headers, and audit event emission.