Files
skills/docs/api-stack/api/api-tenant-provider-memory/src/provider/provider.md
2026-05-29 08:20:56 +01:00

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 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

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 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.