4.4 KiB
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<TenantId, TenantRecord> for deterministic list() output and O(1)
lookup/count.
Features
- Conforms verbatim to
runTenantProviderConformancefrom the abstract contract — same active/suspended/archived filtering rules, samecount()semantics, same cursor-based pagination, same idempotentdispose(). - Optional
tenantsseed validated throughtenantRecordSchemaat the boundary; duplicate ids in the seed raiseTENANT_PROVIDER_MEMORY.DUPLICATE_TENANT. - Out-of-band admin helpers —
add,remove,clear— for runtime mutation of the backing store. These sit alongside the abstractTenantProviderinterface so DB-backed adapters can ship a different (or no) mutation surface without breaking the contract. - Cursor pagination uses the canonical
encodeCursor/decodeCursorhelpers from@suaveplan/api-tenant-provider/pagination, so cursors produced by other adapters that share the encoding round-trip cleanly. ListOpts.pageSizeclamps a single iteration's yield count, mirroring the DB-adapter contract for fan-out callers that want to checkpoint long enumerations.
Usage: Basic
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:
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 exposesadd(record: TenantRecord): void,remove(tenantId: TenantId): void,clear(): void.list(opts?): filters outarchived, includessuspendedonly whenopts.includeSuspendedistrue, appliespageSizeclamp, resumes fromcursorwhen set. Invalid cursors raise the contract'sTENANT_PROVIDER.INVALID_CURSOR.count(): returns the number ofactiverecords.lookup(id): returns the record (includingarchived) ornull.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.