# plugin/plugin — createContactsPlugin factory _(no submodules)_ ## Purpose Returns the `PluginDescriptor` consumed by the `@suaveplan/api` plugin host. Runs the 5-step `onInit` lifecycle: 1. Validate the supplied options through Zod (`INVALID_INPUT` on fail). 2. Resolve the host services (`app`, `db`, optional `openAPIRegistry`, optional `migrationRunner`). 3. Apply the bundled SurrealQL schema unless `applySchemaOnInit: false`. 4. Construct the three SurrealDB repositories and register them in the `ServiceRegistry`. 5. Install a single mount-pattern middleware that injects repositories + `eventBus` into every request context, then register the 18 `/contacts` + `/groups` routes via `definePluginRoutes`. The descriptor declares `dependencies: ["database", "auth"]` so the host topo-sorts after `@suaveplan/api-plugin-database` and `@suaveplan/api-plugin-auth`. ## Features + Single factory entry-point; idempotent within a process (re-calling with the same options is safe — every `onInit` build is a fresh bundle). + ServiceRegistry slot registration: `contactRepository`, `groupRepository`, `membershipRepository`. + Hono ContextVariableMap injection: same three slots + `eventBus`. + Route-prefix collision detection raises `ROUTE_PREFIX_COLLISION`. + Bare-mount fallback when `openAPIRegistry` is absent (routes still work; the OpenAPI document just excludes them). ## Usage: Basic ```ts import { createContactsPlugin } from "@suaveplan/api-plugin-contacts"; const plugin = createContactsPlugin(); // pluginManager.use(plugin); ``` ## Usage: Advanced ```ts import { createContactsPlugin } from "@suaveplan/api-plugin-contacts"; const plugin = createContactsPlugin({ routePrefix: "/api/v1", maxImportBatchSize: 500, maxGroupDepth: 5, enforceGroupDepth: true, applySchemaOnInit: false, // host has its own migration pipeline }); ``` ## API Reference + `createContactsPlugin(options?: ContactsPluginOptions): PluginDescriptor` + `runOnInit(ctx, config): Promise` — the lifecycle body; exported for testability so tests can inject a hand-built `PluginContext`. ## Implementation Notes + The mount-pattern is computed as `*` when `routePrefix === "/"` and `${prefix}/*` otherwise; this matches the convention used by the user plugin and avoids the off-by-one Hono ignores trailing `/`. + ROUTE_PREFIX_COLLISION uses `${prefix}/contacts` as the probe so a second mount of this plugin (even with a different prefix on the same registry) trips the check. + The descriptor's `defaults` slot is the post-Zod-parse `ContactsPluginConfig`, frozen by the schema's `passthrough: false` semantics.