first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
# repositories/membership-repository — SurrealDB-backed MembershipStore
## Purpose
Concrete implementation of the `MembershipStore` interface
from `@suaveplan/contacts`, backed by `SurrealAdapter`. Persists the
many-to-many `(groupId, contactId)` join with a `UNIQUE` SurrealQL
index that raises `MEMBERSHIP_EXISTS` on duplicate insertion.
The repository owns no business logic — idempotent add semantics are
implemented by the route layer (`POST /groups/:id/members` checks
`exists` before adding so the second call returns `{ added: 0 }`
without surfacing a `MEMBERSHIP_EXISTS` error).
## Features
- Full `MembershipStore` surface: `generateId`, `add`, `remove`,
`getByGroup`, `getByContact`, `exists`, `removeAllForContact`,
`removeAllForGroup`, `getContactIdsByGroups`, `countByGroup`.
- Sniffs SurrealDB unique-index violations and translates them into
`ContactsApiError(MEMBERSHIP_EXISTS)`; other adapter errors become
`CREATE_FAILED` / `DELETE_FAILED`.
- `getContactIdsByGroups([])` short-circuits to avoid a useless
database round-trip.
## Usage: Basic
```ts
import { SurrealMembershipRepository } from "@suaveplan/api-plugin-contacts";
const repo = new SurrealMembershipRepository(adapter);
await repo.add({
id: repo.generateId(),
groupId: "contact_group:engineering",
contactId: "contact:ada",
addedAt: new Date(),
});
```
## Usage: Advanced
```ts
import { SurrealMembershipRepository } from "@suaveplan/api-plugin-contacts";
const repo = new SurrealMembershipRepository(adapter, {
idGenerator: () => crypto.randomUUID(),
});
const contactIds = await repo.getContactIdsByGroups([
"contact_group:engineering",
"contact_group:sales",
]);
const unique = Array.from(new Set(contactIds));
```
## API Reference
- `interface SurrealMembershipRepositoryOptions` — `{ idGenerator? }`.
- `class SurrealMembershipRepository implements MembershipStore` —
constructor `(db: SurrealAdapter, options?)`.
## Implementation Notes
- `add` inspects the thrown error's message for the substrings
`unique`, `UNIQUE`, or `duplicate` to classify a unique-index
violation. SurrealDB's exact message format has drifted between
versions; the substring approach survives the most common variants.
- `exists` uses `count() AS count … GROUP ALL` rather than
`SELECT … LIMIT 1` because `count` is consistently zero-or-one in
SurrealDB and avoids deserialising the row.
- `mapMembership` preserves the original `metadata` field shape —
`undefined` stays `undefined` rather than being coerced to `{}`, so
callers can distinguish "no metadata" from "empty metadata".