Files
2026-05-27 23:29:53 +01:00

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 create and update; collisions raise TenantPluginError(TENANT_SLUG_TAKEN) (HTTP 409).
  • Missing-id updates raise TenantPluginError(TENANT_NOT_FOUND) (HTTP 404).
  • delete is soft (sets status: "deleted") and idempotent — calling twice or against a missing id is a no-op.
  • listAll supports optional status filter plus limit / offset pagination.
  • clear() and size test 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 on status, limit, offset.
  • create(input) — Promise<Tenant>; throws TENANT_SLUG_TAKEN on duplicate slug.
  • update(id, patch) — Promise<Tenant>; throws TENANT_NOT_FOUND for unknown ids or TENANT_SLUG_TAKEN if the patched slug collides.
  • delete(id) — Promise<void>; soft + idempotent.
  • size — number of 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.