import type { Tenant } from "@suaveplan/api-tenant"; import { TENANT_PLUGIN_CODES, TenantPluginError, } from "../error-codes/error-codes.js"; import type { CreateTenantInput, ListTenantsQuery, TenantStore, UpdateTenantInput, } from "./store.js"; /** Test- and small-ops-friendly in-memory `TenantStore` implementation. */ export class MemoryTenantStore implements TenantStore { private readonly tenants = new Map(); private nextSeq = 1; public async getById(id: string): Promise { return this.tenants.get(id) ?? null; } public async getBySlug(slug: string): Promise { for (const t of this.tenants.values()) { if (t.slug === slug) return t; } return null; } public async getByDomain(domain: string): Promise { for (const t of this.tenants.values()) { if (t.domains.includes(domain)) return t; } return null; } public async listAll(query?: ListTenantsQuery): Promise { let rows = Array.from(this.tenants.values()); if (query?.status !== undefined) { rows = rows.filter((t) => t.status === query.status); } const offset = query?.offset ?? 0; const limit = query?.limit ?? rows.length; return rows.slice(offset, offset + limit); } public async create(input: CreateTenantInput): Promise { for (const t of this.tenants.values()) { if (t.slug === input.slug) { throw new TenantPluginError( TENANT_PLUGIN_CODES.TENANT_SLUG_TAKEN, `Tenant slug '${input.slug}' is already taken`, { context: { slug: input.slug, statusCode: 409 } } ); } } const id = `tenant_${this.nextSeq++}`; const now = new Date(); const tenant: Tenant = { id, name: input.name, slug: input.slug, status: input.status ?? "active", settings: input.settings ?? {}, enabledPlugins: input.enabledPlugins ? [...input.enabledPlugins] : [], domains: input.domains ? [...input.domains] : [], createdAt: now, updatedAt: now, ...(input.plan !== undefined ? { plan: input.plan } : {}), ...(input.metadata !== undefined ? { metadata: input.metadata } : {}), }; this.tenants.set(id, tenant); return tenant; } public async update(id: string, patch: UpdateTenantInput): Promise { const existing = this.tenants.get(id); if (!existing) { throw new TenantPluginError( TENANT_PLUGIN_CODES.TENANT_NOT_FOUND, `Tenant '${id}' not found`, { context: { id, statusCode: 404 } } ); } if (patch.slug !== undefined && patch.slug !== existing.slug) { for (const t of this.tenants.values()) { if (t.id !== id && t.slug === patch.slug) { throw new TenantPluginError( TENANT_PLUGIN_CODES.TENANT_SLUG_TAKEN, `Tenant slug '${patch.slug}' is already taken`, { context: { slug: patch.slug, statusCode: 409, }, } ); } } } const updated: Tenant = { ...existing, ...(patch.name !== undefined ? { name: patch.name } : {}), ...(patch.slug !== undefined ? { slug: patch.slug } : {}), ...(patch.status !== undefined ? { status: patch.status } : {}), ...(patch.plan !== undefined ? { plan: patch.plan } : {}), ...(patch.enabledPlugins !== undefined ? { enabledPlugins: [...patch.enabledPlugins] } : {}), ...(patch.domains !== undefined ? { domains: [...patch.domains] } : {}), ...(patch.settings !== undefined ? { settings: patch.settings } : {}), ...(patch.metadata !== undefined ? { metadata: patch.metadata } : {}), updatedAt: new Date(), }; this.tenants.set(id, updated); return updated; } public async delete(id: string): Promise { const existing = this.tenants.get(id); if (!existing) { // Idempotent — deleting an unknown id is a no-op. return; } this.tenants.set(id, { ...existing, status: "deleted", updatedAt: new Date(), }); } /** Test helper — number of stored entries. */ public get size(): number { return this.tenants.size; } /** Test helper — drop everything. */ public clear(): void { this.tenants.clear(); this.nextSeq = 1; } }