# repositories/contact-repository — SurrealDB-backed ContactStore ## Purpose Concrete implementation of the universal `ContactStore` interface from `@suaveplan/contacts`, backed by a connected `SurrealAdapter`. Wraps every adapter call in `try/catch` blocks that re-throw failures as `ContactsApiError` with the appropriate `CREATE_FAILED` / `UPDATE_FAILED` / `DELETE_FAILED` code so callers can discriminate without sniffing the underlying SurrealDB driver's exception text. The repository is **stateless aside from the adapter handle** — the adapter's lifecycle (connect, disconnect, namespace selection) is the host's responsibility. Multiple repositories may share one adapter. ## Features - Full `ContactStore` surface: `create`, `getById`, `update`, `delete`, `list` (search / status / channelType / tags / paging), `findByChannelValue`, `findByIds`, `countByTenant`. - Channel-typed list filter — `channels[WHERE type = $type]` uses Surreal's array projection so filtering is index-pushable. - Date hydration — string `createdAt` / `updatedAt` values returned by SurrealDB are parsed into native `Date` instances. - Bare ids (`abc`) are auto-prefixed with `contact:` so callers can pass either form. - Failures are wrapped in the typed `ContactsApiError` so HTTP handlers branch on `error.code`. ## Usage: Basic ```ts import { SurrealAdapter } from "@suaveplan/surrealdb"; import { SurrealContactRepository } from "@suaveplan/api-plugin-contacts"; const adapter = new SurrealAdapter({ url, namespace, database }); await adapter.connect(); const repo = new SurrealContactRepository(adapter); const c = await repo.create({ tenantId: "tenant-1", displayName: "Ada Lovelace", channels: [ { type: "email", value: "ada@example.com", primary: true, verified: false }, ], }); ``` ## Usage: Advanced ```ts import { SurrealContactRepository } from "@suaveplan/api-plugin-contacts"; const repo = new SurrealContactRepository(adapter); // Paginated list with channelType filter: const page = await repo.list({ tenantId: "tenant-1", channelType: "email", search: "@example.com", limit: 50, offset: 0, }); // Bulk fetch by ids: const byIds = await repo.findByIds(page.contacts.map((c) => c.id)); ``` ## API Reference - `class SurrealContactRepository implements ContactStore` — constructor `(db: SurrealAdapter)`. Public methods: - `create(input: CreateContactInput): Promise` - `getById(id: string): Promise` - `update(id: string, input: UpdateContactInput): Promise` - `delete(id: string): Promise` - `list(query: ContactQuery): Promise` - `findByChannelValue(tenantId, channelType, channelValue): Promise` - `findByIds(ids: readonly string[]): Promise` - `countByTenant(tenantId: string): Promise` ## Implementation Notes - `list` builds the WHERE clause with positional `$name` parameters and passes them to `SurrealAdapter.query` for prepared-statement style binding — no string interpolation of user input. - `findByIds([])` short-circuits with an empty result and never touches the adapter so callers don't pay for a SurrealDB round-trip on an empty input. - `mapContact` strips `null` first/last names before they hit the returned `Contact` (the type marks them optional), matching the universal `InMemoryContactStore` shape exactly.