Initial Commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# 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<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.
|
||||
Reference in New Issue
Block a user