3.1 KiB
memory
Purpose
Ships the default MemoryTenantStore — a synchronous in-memory
implementation of the TenantStore interface suitable for tests,
ephemeral demos, and very small ops deployments where a database is
overkill. The store enforces slug uniqueness, performs soft-deletes
(by flipping status to "deleted" rather than evicting the row),
and assigns monotonic ids so test snapshots stay stable.
Features
- Slug uniqueness enforced on
createandupdate; collisions raiseTenantPluginError(TENANT_SLUG_TAKEN)(HTTP 409). - Missing-id updates raise
TenantPluginError(TENANT_NOT_FOUND)(HTTP 404). deleteis soft (setsstatus: "deleted") and idempotent — calling twice or against a missing id is a no-op.listAllsupports optionalstatusfilter pluslimit/offsetpagination.clear()andsizetest helpers for assertion convenience.
Usage: Basic Example
import { MemoryTenantStore } from "@suaveplan/api-plugin-tenant";
const store = new MemoryTenantStore();
const tenant = await store.create({ name: "Acme", slug: "acme" });
console.log(tenant.id); // "tenant_1"
console.log(await store.getBySlug("acme"));
Usage: Advanced Example
import { MemoryTenantStore } from "@suaveplan/api-plugin-tenant";
const store = new MemoryTenantStore();
await store.create({
name: "Acme",
slug: "acme",
domains: ["acme.example.com"],
enabledPlugins: ["billing", "vault"],
settings: { features: { betaUI: true } },
});
await store.create({ name: "Bravo", slug: "bravo", status: "pending" });
const active = await store.listAll({ status: "active", limit: 50 });
const byDomain = await store.getByDomain("acme.example.com");
await store.delete(byDomain!.id);
const after = await store.getById(byDomain!.id);
console.assert(after?.status === "deleted");
API Reference
class MemoryTenantStore implements TenantStore— constructor takes no arguments.getById(id)/getBySlug(slug)/getByDomain(domain)—Promise<Tenant | null>.listAll(query?)—Promise<readonly Tenant[]>; optional filter onstatus,limit,offset.create(input)—Promise<Tenant>; throwsTENANT_SLUG_TAKENon duplicate slug.update(id, patch)—Promise<Tenant>; throwsTENANT_NOT_FOUNDfor unknown ids orTENANT_SLUG_TAKENif the patched slug collides.delete(id)—Promise<void>; soft + idempotent.size—numberof stored entries (deleted included).clear()—void; drop everything and reset the id sequence.
Implementation Notes
The id sequence is monotonic and starts at 1 so test fixtures stay
deterministic across runs (no crypto.randomUUID calls). Backing
storage is a single Map<string, Tenant> so all operations are O(n)
for listAll and getBySlug / getByDomain — perfectly fine for the
test scale this store targets. Operators running thousands of tenants
should swap in the SurrealDB or Postgres implementations from the
companion packages. The store does NOT broadcast change events; the
plugin emits its own OTel counter via tenantService instead.