first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# `plugin/plugin.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Owns `createSitePlugin`, the canonical factory that produces the `PluginDescriptor` consumed by `@suaveplan/api-plugin`'s `PluginRegistry`. The factory composes every other module in this package — `SitePluginOptionsSchema` validates options at the boundary, the `SiteService` + `DomainResolverService` are constructed once, the Hono middleware is built and exposed through `ServiceRegistry`, the public + private route trees are mounted under the configured `routePrefix`, and the optional `organization:created` listener is wired when `autoCreateDefaultSite` is on. This file is the single integration point — every consumer of the package builds its descriptor through this one function.
The lifecycle is the canonical 4-phase shape: validate-at-construction, build-services in `onInit`, mount-routes in `onReady`, drop-listener in `onClose`. Each phase fails fast with a typed `SiteError` carrying a `SITE.*` code so misconfigurations are visible at registry intake rather than deferred to the first request.
## Features
- Zod-validated option boundary — `SITE.INVALID_OPTIONS` on bad config.
- Resolves `db` from `ServiceRegistry`; throws `SITE.SERVICE_NOT_AVAILABLE` when missing.
- Constructs and registers `siteService`, `domainResolver`, `domainResolverMiddleware`.
- Optional `organization:created` listener for default-site auto-bootstrap, with `SITE.BOOTSTRAP_FAILED` event emission on error and the `site.bootstrap_failed_total` counter incremented on each failure.
- Route mounting under `config.routePrefix` (defaults to `/api`) with prefix-collision detection via `SITE.ROUTE_PREFIX_COLLISION`.
- `optionalDependencies: ["database", "auth", "events"]` so orchestrators topo-sort the site plugin after siblings without making them mandatory.
- Emits `site:initialized` and `site:ready` lifecycle events.
## Usage: Basic
```ts
import { createSitePlugin } from "@suaveplan/api-plugin-site";
registry.register(createSitePlugin({ maxSitesPerOrg: 25 }));
```
## Usage: Advanced
```ts
import { createSitePlugin } from "@suaveplan/api-plugin-site";
const plugin = createSitePlugin({
routePrefix: "/v1",
autoCreateDefaultSite: true,
defaultDomainPattern: "{slug}.acme.com",
enableDomainResolution: true,
});
await registry.register(plugin, { db: surrealAdapter, app: openApiHono });
await registry.start();
// Downstream plugin can now resolve the service:
const siteService = ctx.getService("siteService")!;
await siteService.create({
organization_id: "organization:abc",
name: "Customer Site",
domains: ["customer.acme.com"],
});
```
## API Reference
- `createSitePlugin(options?: CreateSitePluginOptions): PluginDescriptor<SitePluginConfig>`
- `type CreateSitePluginOptions = SitePluginOptionsInput`
- The descriptor name is `"site"`, version `"0.1.0"`.
## Implementation Notes
- Service handles are stored in closure-scoped variables and bound via a private `bind` helper so the `onReady` hook can reuse the same instances constructed in `onInit`. The `mustService` / `mustResolver` guards trip when an orchestrator runs `onReady` before `onInit` (which the canonical registry never does, but tests / misuse can).
- The plugin treats the host event bus as `EventBusLike<SiteEventMap>` so the cumulative event typing is enforced inside the plugin without bleeding into the global `EventBusLike` declaration on the host.
- Route mounting is keyed by detecting any existing path starting with `${prefix}/sites/`; a collision raises `SITE.ROUTE_PREFIX_COLLISION` so two plugins can't silently overwrite each other.
- `enableDomainResolution: false` suppresses ONLY the public router (which contains the resolve endpoint); the private router always mounts so CRUD remains usable even when public lookup is disabled.