8.8 KiB
Add @suaveplan/api-plugin-site
Package: @suaveplan/api-plugin-site
Status: PROPOSED
Type: new-package (migration from suaveplan)
- Spec delta: ./specs/api-plugin-site/spec.md
Why
Genesis needs the multi-site / multi-domain capability @suaveplan/api-plugin-site@1.0.21 ships upstream so consumer apps can persist site records (name / slug / theme / settings / push) under SurrealDB, resolve incoming Host headers to a site through a single canonical service, and expose public + private OpenAPI Hono routes for site management. The upstream package additionally bootstraps a default site on organization:created and emits site:settings:updated whenever settings change so CDNs and downstream consumers can react.
The upstream library exists at /mnt/s/web/suaveplan/packages/server/api-plugin-site (11 src files, partial test coverage, three as any casts in plugin.ts, runtime-monkey-patched updateSettings, missing-domain bug in addDomain, no OTel surface, no typed error registry). This change lands a single, enterprise-grade @suaveplan/api-plugin-site@0.1.0 that fixes every flagged smell from the upstream enterprise audit (/mnt/s/web/suaveplan/enterprise/api-plugin-site.md) at parity with the upstream feature surface.
What changes
- ADDED:
SITE_CODES/SiteError—defineCodes("SITE", […])registry of 14 codes (INVALID_OPTIONS,SERVICE_NOT_AVAILABLE,SITE_NOT_FOUND,SITE_LIMIT_EXCEEDED,DOMAIN_LIMIT_EXCEEDED,DOMAIN_ALREADY_CLAIMED,LAST_DOMAIN_REMOVAL,DOMAIN_NOT_FOUND,INVALID_DOMAIN,VALIDATION,BOOTSTRAP_FAILED,STORAGE_ERROR,ROUTE_PREFIX_COLLISION,RESOLUTION_FAILED) extendingSuaveplanError. - ADDED:
SiteSchema,CreateSiteSchema,UpdateSiteSchema (.strict),SitePublicInfoSchema,SiteIdParamSchema,SiteSettingsSchema,SiteThemeSchema,SiteStateNumberSchema— inlined Zod surface for the site entity. - ADDED:
DomainSchema,AddDomainRequestSchema/ResponseSchema,RemoveDomainRequestSchema/ResponseSchema,DomainResolveParamSchema/ResponseSchema— inlined Zod surface for the domain entity. - ADDED:
ApplePushConfigSchema,FirebasePushConfigSchema,PushConfigSchema,PushConfigValidationResponseSchema,validatePushConfig— push-config schema + pure validator mirroring the SurrealDBfn::site::push::validate. - ADDED:
SitePluginOptionsSchema,SitePluginConfig,SitePluginOptionsInput,DEFAULT_CONFIG,mergeConfig— Zod-validated boundary for plugin options with frozen defaults. - ADDED:
normalizeDomain— pure, mirroringfn::site::domain::normalize. - ADDED:
SiteDb— structural db port consumed by services (positional-bindquery+ typedcreate/update). - ADDED:
SiteService— canonical CRUD:create,createDefaultSite,getById,requireById,listByOrganization,countByOrganization,update,updateSettings,updateState,addDomain,removeDomain,archive,getActive,search. Emitssite:settings:updatednatively (no monkey-patching). Caps enforced via plugin config. Bugfix:addDomainnow persists the new domain (upstream produced an unused local). - ADDED:
DomainResolverService—resolveenvelope,isDomainTaken,resolveOrThrow. Wraps SurrealDBfn::domain::search. Emits OTel counter + duration histogram. - ADDED:
createDomainResolverMiddleware— Hono middleware factory injectingsite/organization/siteId/organizationIdinto request context. - ADDED:
createPublicSiteRoutes—GET /sites/resolve/{domain},GET /sites/{id}/public. - ADDED:
createPrivateSiteRoutes—POST /sites,PATCH /sites/{id},POST /sites/{id}/domains,DELETE /sites/{id}/domains. - ADDED:
createSitePlugin—definePlugin<SitePluginConfig>factory wrapping the genesisPluginDescriptorwith the canonical 4-phase lifecycle. Subscribes toorganization:createdwhenautoCreateDefaultSiteis on, emitssite:bootstrap:failedon failure, registers three services (siteService,domainResolver,domainResolverMiddleware), mounts route trees under configurableroutePrefix. - ADDED:
getSiteInstruments/resetSiteInstruments/SiteInstruments— OTel bundle (5 instruments) with cache reset for test rebinding. - ADDED:
services/registry-augmentation.ts— declaressiteService,domainResolver,domainResolverMiddleware,app,dbonServiceRegistry. - ADDED:
surql/tables/site.surql— per-table migration with state constants, indices, and thefn::domain::searchfunction.
Source mapping
| Upstream source | Genesis location | Notes |
|---|---|---|
src/index.ts |
src/index.ts |
Re-exports broadened to expose every documented symbol. |
src/plugin.ts |
src/plugin/plugin.ts |
Rewritten with definePlugin<SitePluginConfig>() from @suaveplan/api-plugin; every as any removed; monkey-patch dropped; site:bootstrap:failed event added. |
src/config.ts |
src/schemas/options.ts |
Switched from hand-rolled mergeConfig to Zod-validated boundary; DEFAULT_CONFIG frozen. |
src/middleware/domain-resolver.middleware.ts |
src/middleware/domain-resolver.ts |
Uses normalizeDomain before resolve. |
src/routes/public.routes.ts |
src/routes/public-routes.ts |
OpenAPI route definitions preserved; sanitised public projection. |
src/routes/private.routes.ts |
src/routes/private-routes.ts |
Adds DELETE /sites/{id}/domains; bodies Zod-validated through @hono/zod-openapi. |
src/services/site.service.ts |
src/service/site.ts |
addDomain persistence bug fixed; event emit moved into the service; OTel counters added; structural SiteDb port. |
src/services/domain-resolver.service.ts |
src/service/domain-resolver.ts |
OTel counter + histogram; codes embedded in result envelope; resolveOrThrow helper. |
src/types/site.types.ts |
src/schemas/site.ts + src/schemas/domain.ts + src/schemas/push.ts + src/types/types.ts |
Type-only file split into Zod schemas (per-feature) and runtime-free types. |
surql/tables/site.surql |
surql/tables/site.surql |
Trimmed fn::site::* helpers that the application-tier now owns (normalizeDomain, createDefaultSite) — covered by ### Excluded. |
Excluded from this migration
The genesis 0.1.0 ships at functional parity with the upstream public surface (services + routes + middleware + plugin + migration). The §3 enterprise-doc enhancements that depend on packages not yet landed on genesis are scoped out with concrete justifications — NOT silently dropped:
fn::site::*SurQL helper suite (e.g.fn::site::create,fn::site::push::validate,fn::site::settings::update) — application-tierSiteServicenow owns every call site. Keeping those helpers in the migration would duplicate logic between two writers and contradict §0a's "no implicit drops"; the application tier IS the canonical writer in genesis (matching the recentapi-plugin-import/api-plugin-searchprecedent). The migration ships onlyfn::domain::searchbecause the resolver still delegates pattern matching to the database.- JWT / permissions / rate-limit / audit middleware wiring — depends on
@suaveplan/api-plugin-rate-limit+@suaveplan/api-plugin-auditalready landed in genesis, but the integration knob is the host app'sapp.use(prefix, middleware)Hono pattern. The site plugin does NOT hard-depend on them so excluding integration from THIS migration does not block consumers from layering them on top. - ACME / Let's Encrypt automation, edge-CDN cache busting, sitemap auto-gen, theme draft/publish workflow — every item is a §4 future enhancement, not a §2 current-state capability. None are part of the upstream
1.0.21shipped feature surface. - Benchmarks under
src/__benchmarks__/— the monorepo benchmark wave is a separate gate; the site plugin will receive its mitata harness in that wave.
Migration
No external consumers exist yet (genesis-only). For internal consumers:
- Old:
import { createSitePlugin } from "@suaveplan/api-plugin-site"from the upstream1.0.xline. - New: same import, but the descriptor's lifecycle hooks now use the genesis
PluginContext(typedgetOwnConfig, augmentedServiceRegistry["siteService"|"domainResolver"|"domainResolverMiddleware"]). - Event-bus events are now strongly typed via
SiteEventMap; consumers that wrotebus.on("site:settings:updated" as any, ...)should drop the cast. - The
addDomainbug-fix means a site'sdomainsarray is actually updated after a successful call. Consumers that worked around the bug by manually rewriting the row should remove the workaround.
Impact
Affected dependents
bunx turbo ls --filter=...@suaveplan/api-plugin-site enumerates zero dependents on genesis today (this is the first landing). The package will be picked up by future genesis-original packages (e.g. api host orchestrator). No dependent sweep required for this change.