# repositories/group-repository — SurrealDB-backed GroupStore ## Purpose Concrete implementation of the `GroupStore` interface from `@suaveplan/contacts`, backed by `SurrealAdapter`. Implements the hybrid adjacency-list + materialised-path strategy for hierarchical groups: every row carries `parentId`, `path`, and `depth`, so subtree queries become `WHERE string::contains(path, $path)` and lookups of direct children become `WHERE parentId = $parentId`. Enforces the plugin's configured `maxGroupDepth` on `createGroup` and `moveGroup` when `enforceGroupDepth` is true — closing the upstream gap where the config was advertised but never enforced. ## Features - Full `GroupStore` surface: `generateId`, `create`, `getById`, `update`, `delete`, `list`, `getChildren`, `getDescendants`, `getRoots`, `findByIds`. - Domain-friendly `createGroup` that computes `path` and `depth` from the optional parent, plus a low-level `create` that takes a pre-built `Group`. - `moveGroup(id, newParentId, tenantId)` — reparent with depth + path recomputation and depth-cap enforcement. - Bare ids (`g1`) are auto-prefixed with `contact_group:`. - Configurable `maxGroupDepth` + `enforceGroupDepth` via constructor options. ## Usage: Basic ```ts import { SurrealGroupRepository } from "@suaveplan/api-plugin-contacts"; const repo = new SurrealGroupRepository(adapter, { maxGroupDepth: 10, enforceGroupDepth: true, }); const root = await repo.createGroup({ tenantId: "t", name: "All" }); const child = await repo.createGroup({ tenantId: "t", name: "Engineering", parentId: root.id, }); ``` ## Usage: Advanced ```ts import { SurrealGroupRepository } from "@suaveplan/api-plugin-contacts"; const repo = new SurrealGroupRepository(adapter, { maxGroupDepth: 5, enforceGroupDepth: true, idGenerator: () => crypto.randomUUID(), }); // Reparent — throws GROUP_DEPTH_EXCEEDED if depth would exceed 5: const moved = await repo.moveGroup(child.id, otherParentId, "t"); // Walk a subtree: const descendants = await repo.getDescendants(root.path); ``` ## API Reference - `interface SurrealGroupRepositoryOptions` — `{ maxGroupDepth?, enforceGroupDepth?, idGenerator? }`. - `class SurrealGroupRepository implements GroupStore` — constructor `(db: SurrealAdapter, options?: SurrealGroupRepositoryOptions)`. Adds two domain methods on top of the interface: - `createGroup(input: CreateGroupInput): Promise` — computes path/depth + enforces depth cap. - `moveGroup(id, newParentId, tenantId): Promise` — reparent + depth cap. ## Implementation Notes - The two-step write (`CREATE` then `UPDATE path`) is used because SurrealDB assigns the record id at `CREATE` time, and the materialised path needs the actual id. The temporary id is stripped in the follow-up patch. - `getDescendants` uses `string::contains(path, $path) AND path != $path` so the root itself is not included in the result set. - Constructor option `enforceGroupDepth` defaults to `false` so the bare-class behaviour matches the universal `InMemoryGroupStore` in `@suaveplan/contacts`; the plugin factory sets it to `true` by default to enforce the documented config. - `pickFirst` raises `CREATE_FAILED` when SurrealDB returns no row; this catches database-side rejections (assertion failure on a NOT NULL field, for example) that the driver swallowed into an empty array.