# `createMemoryTenantProvider` _(no submodules)_ ## Purpose In-memory reference implementation of the `@suaveplan/api-tenant-provider` `TenantProvider` contract. It is both the default backing for the API testing kit (so unit tests don't need a database) and a production-grade option for small deployments whose tenant list is small and static, or where a process-restart-bounded seed is acceptable. Backed by an insertion-ordered `Map` for deterministic `list()` output and `O(1)` `lookup`/`count`. ## Features - Conforms verbatim to `runTenantProviderConformance` from the abstract contract — same active/suspended/archived filtering rules, same `count()` semantics, same cursor-based pagination, same idempotent `dispose()`. - Optional `tenants` seed validated through `tenantRecordSchema` at the boundary; duplicate ids in the seed raise `TENANT_PROVIDER_MEMORY.DUPLICATE_TENANT`. - Out-of-band admin helpers — `add`, `remove`, `clear` — for runtime mutation of the backing store. These sit alongside the abstract `TenantProvider` interface so DB-backed adapters can ship a different (or no) mutation surface without breaking the contract. - Cursor pagination uses the canonical `encodeCursor` / `decodeCursor` helpers from `@suaveplan/api-tenant-provider/pagination`, so cursors produced by other adapters that share the encoding round-trip cleanly. - `ListOpts.pageSize` clamps a single iteration's yield count, mirroring the DB-adapter contract for fan-out callers that want to checkpoint long enumerations. ## Usage: Basic ```ts import { createMemoryTenantProvider } from "@suaveplan/api-tenant-provider-memory/provider"; const provider = createMemoryTenantProvider({ tenants: [ { id: "acme", status: "active", createdAt: "2026-01-01T00:00:00Z" }, { id: "beta", status: "active", createdAt: "2026-01-02T00:00:00Z" }, ], }); for await (const tenant of provider.list()) { console.log(tenant.id); } await provider.dispose(); ``` ## Usage: Advanced Cursor-based fan-out with checkpointing, plus runtime mutation: ```ts import { encodeCursor } from "@suaveplan/api-tenant-provider/pagination"; import { createMemoryTenantProvider } from "@suaveplan/api-tenant-provider-memory/provider"; const provider = createMemoryTenantProvider(); provider.add({ id: "acme", status: "active", createdAt: "2026-01-01T00:00:00Z" }); provider.add({ id: "beta", status: "suspended", createdAt: "2026-01-02T00:00:00Z" }); // First page — active only for await (const tenant of provider.list({ pageSize: 1 })) { console.log(tenant.id); } // Second page — resume from offset const cursor = encodeCursor({ offset: 1, version: 1 }); for await (const tenant of provider.list({ cursor, includeSuspended: true })) { console.log(tenant.id); } provider.remove("acme"); provider.clear(); ``` ## API Reference - `createMemoryTenantProvider(options?: CreateMemoryTenantProviderOptions): MemoryTenantProvider`. - `CreateMemoryTenantProviderOptions`: `{ tenants?: readonly TenantRecord[] }`. - `MemoryTenantProvider extends TenantProvider`: also exposes `add(record: TenantRecord): void`, `remove(tenantId: TenantId): void`, `clear(): void`. - `list(opts?)`: filters out `archived`, includes `suspended` only when `opts.includeSuspended` is `true`, applies `pageSize` clamp, resumes from `cursor` when set. Invalid cursors raise the contract's `TENANT_PROVIDER.INVALID_CURSOR`. - `count()`: returns the number of `active` records. - `lookup(id)`: returns the record (including `archived`) or `null`. - `dispose()`: idempotent — clears the backing store. ## Implementation Notes The backing `Map` preserves insertion order per the ECMAScript spec, so `list()` yields tenants in the order they were seeded or added — no sort is required. `dispose` clears the map rather than flagging a disposed bit, which means subsequent `lookup`/`count` calls behave as if the provider were freshly constructed with an empty seed; this is deliberate (the conformance harness requires `dispose` to remain callable). Admin helpers (`add`/`remove`) re-validate inputs through `tenantRecordSchema` so a runtime-mutated provider has the same boundary guarantees as one seeded via the constructor. The `pageSize` clamp is applied at iterator construction (when `Symbol.asyncIterator` is invoked) so a caller can construct multiple iterables from a single provider without sharing state.