Initial Commit
This commit is contained in:
82
packages/api-stack/api/api-plugin-tenant/src/store/memory.md
Normal file
82
packages/api-stack/api/api-plugin-tenant/src/store/memory.md
Normal 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.
|
||||
Reference in New Issue
Block a user