Files
2026-05-27 23:29:53 +01:00

7.3 KiB

Migrate @suaveplan/api-plugin-contacts to genesis at enterprise quality

Package: @suaveplan/api-plugin-contacts Status: PROPOSED Type: new-package

Why

@suaveplan/api-plugin-contacts is the HTTP face of @suaveplan/contacts. The upstream version on main shipped to 1.0.0 with ~30 as any casts in route handlers, untyped buildTree, no shared error hierarchy, no OTel instrumentation, no real-database integration tests, and maxGroupDepth declared in config but never enforced. Genesis policy forbids all of those defects, so the package is being rebuilt at 0.1.0 against the genesis @suaveplan/contacts store interfaces (ContactStore / GroupStore / MembershipStore with generateId) rather than the upstream port. Migrating closes the contacts plugin family — the first two siblings (@suaveplan/contacts and @suaveplan/macos-contacts) are already at gold standard.

What changes

  • ADDED: createContactsPlugin(options?: ContactsPluginOptions): PluginDescriptor<ContactsPluginConfig> — factory mounting /contacts and /groups route trees on the host OpenAPIHono.
  • ADDED: ContactsPluginOptionsSchema, ContactsPluginOptions, ContactsPluginConfig — Zod-validated configuration.
  • ADDED: SurrealContactRepository, SurrealGroupRepository, SurrealMembershipRepository — @suaveplan/contacts store-interface implementations over SurrealAdapter.
  • ADDED: CONTACTS_API_CODES / ContactsApiError (@suaveplan/error based) — 12 stable codes covering every failure path.
  • ADDED: getContactsApiInstruments / resetContactsApiInstruments — 6 OTel instruments (4 counters + 2 histograms) emitted from production hot paths.
  • ADDED: Typed Hono ContextVariableMap augmentation — contactRepository, groupRepository, membershipRepository, eventBus slots (eliminates upstream's as any casts).
  • ADDED: ServiceRegistry augmentation — exposes contactRepository / groupRepository / membershipRepository as typed services to sibling plugins.
  • ADDED: SurrealQL schema files for contact, contact_group, group_membership tables and the MigrationRunner integration.
  • ADDED: Bulk-import endpoint honouring maxImportBatchSize with per-row error reporting (Zod-validated).
  • ADDED: Repository-level maxGroupDepth enforcement (createGroup and moveGroup raise CONTACTS_API.GROUP_DEPTH_EXCEEDED).
  • ADDED: Typed buildTree(root: Group, descendants: readonly Group[]): GroupTreeNode helper.
  • ADDED: Integration tests using @suaveplan/testing-integration-surrealdb testcontainers — real Surreal v3, no mocks.

Out of scope

  • CSV / VCF import parsing — already lives in @suaveplan/contacts/import-export/contact-importer; the bulk-import endpoint accepts pre-parsed JSON rows.
  • GDPR export / erase, schema-registry versioning, audit-log integration, rate-limiting, dedup, domain normalisation — enterprise/api-plugin-contacts.md §3-4 list these as plugin-composition improvements. They belong on the host (already mountable via @suaveplan/api-plugin-audit, @suaveplan/api-plugin-rate-limit, @suaveplan/api-plugin-export); duplicating them inside this plugin would re-publish behaviour the host owns.
  • Sync adapters (Google / iCloud / Outlook) and a saved-segments rules engine — enterprise/api-plugin-contacts.md §4 items 8/3, deferred to dedicated plugins.

Source mapping

This change derives from ../suaveplan/enterprise/api-plugin-contacts.md and the upstream source at ../suaveplan/packages/server/api-plugin-contacts/src/.

Requirement Source lines Notes
Re-export ContactsConfigSchema + createContactsPlugin from root L57, L98, L113 verbatim; genesis names them ContactsPluginConfigSchema + createContactsPlugin
maxImportBatchSize (default 1000) and maxGroupDepth (default 10) config L12, L49 verbatim
/contacts + /groups route trees with /groups/:id/members and /groups/:id/resolve L12, L37-L38 upstream files routes/contacts/*, routes/groups/*
SurrealContactRepository, SurrealGroupRepository, SurrealMembershipRepository exported L21 verbatim
Hierarchical group nesting with materialised path + depth L12, L49 upstream group.repository.ts L19-L58
Enforce maxGroupDepth in repository on insert + move L49, L99 upstream gap closed; raises GROUP_DEPTH_EXCEEDED
Bulk-import endpoint honouring maxImportBatchSize with per-row errors L50, L99-L100 upstream routes/contacts/contacts.routes.ts L218-L263 baseline; genesis adds Zod boundary + typed errors
Merge contacts endpoint (channel + tag union) upstream contacts.routes.ts L265-L331 preserved
Group subtree (GET /groups/:id/tree) + reparent (POST /groups/:id/move) upstream groups.routes.ts L206-L270 preserved; buildTree becomes fully typed
Group membership add / remove / list / resolve upstream groups.members.routes.ts L24-L196 preserved
Zod schemas on every HTTP body L79 verbatim; @hono/zod-openapi adapter
Real-SurrealDB integration tests via test:with-db:filter L107, L118 genesis uses @suaveplan/testing-integration-surrealdb testcontainers
@suaveplan/error hierarchy with typed code registry L81, L105 upstream gap closed (12 codes)

Requirements with no source line

Requirement Justification
OpenTelemetry instruments emitted from production paths Genesis-wide observability policy (CLAUDE.md Code Organization) — every plugin emits counters + histograms; not package-specific in upstream enterprise/*.md.
Typed Hono ContextVariableMap augmentation in place of as any casts Closes a defect in the upstream port (~30 as any casts in routes/**) flagged by genesis CLAUDE.md rule 9 (no static-analysis suppression / cast-only escape hatches).
100% Istanbul coverage threshold Genesis-wide policy.
@suaveplan/testing/runner for all test primitives CLAUDE.md rule 11.

Verification approach

  • src/error-codes/error-codes.test.ts — covers code registry stability; pinned by tests/dep-graph.test.ts for SuaveplanError lineage.
  • src/otel/instruments.test.ts — asserts every instrument binds against a real in-memory MeterProvider after resetContactsApiInstruments.
  • src/repositories/contact-repository.test.ts, src/repositories/group-repository.test.ts, src/repositories/membership-repository.test.ts — unit-shape tests for query builder + mapping with an injectable adapter contract.
  • src/routes/contacts/build-routes.test.ts, src/routes/groups/build-routes.test.ts — route-tree composition and shape assertions.
  • src/plugin/plugin.test.ts — plugin descriptor validation, ServiceRegistry slot registration, middleware injection.
  • src/api-plugin-contacts.spec-conformance.test.ts — pins every public-API claim: exported symbols, error-code registry, OTel instrument names, route paths, ServiceRegistry slots.
  • tests/integration/full-boot.test.ts — boots auth + database + contacts plugins against real SurrealDB testcontainers and walks the full CRUD + membership + audience-resolve roundtrip.
  • tests/integration/group-depth.test.ts — pins the maxGroupDepth repository guard against real SurrealDB.
  • tests/integration/bulk-import.test.ts — pins per-row error reporting against real SurrealDB.

Migration impact

None — additive only. No consumer of @suaveplan/api-plugin-contacts exists in genesis yet.