Files
suaveplan/packages/commerce/contacts/api-plugin-contacts/src/repositories/contact-repository.md
2026-05-27 23:29:53 +01:00

3.4 KiB

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

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

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<Contact>
    • getById(id: string): Promise<Contact | null>
    • update(id: string, input: UpdateContactInput): Promise<Contact>
    • delete(id: string): Promise<void>
    • list(query: ContactQuery): Promise<ContactListResult>
    • findByChannelValue(tenantId, channelType, channelValue): Promise<Contact | null>
    • findByIds(ids: readonly string[]): Promise<Contact[]>
    • countByTenant(tenantId: string): Promise<number>

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.