Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
# `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
```ts
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
```ts
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.