3.4 KiB
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
ContactStoresurface: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/updatedAtvalues returned by SurrealDB are parsed into nativeDateinstances. - Bare ids (
abc) are auto-prefixed withcontact:so callers can pass either form. - Failures are wrapped in the typed
ContactsApiErrorso HTTP handlers branch onerror.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
listbuilds the WHERE clause with positional$nameparameters and passes them toSurrealAdapter.queryfor 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.mapContactstripsnullfirst/last names before they hit the returnedContact(the type marks them optional), matching the universalInMemoryContactStoreshape exactly.