Files
skills/docs/commerce/contacts/api-plugin-contacts/src/repositories/group-repository.md
2026-05-29 08:20:56 +01:00

3.4 KiB

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

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

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<Group> — computes path/depth + enforces depth cap.
    • moveGroup(id, newParentId, tenantId): Promise<Group> — 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.