first commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# `createMemoryTenantProvider`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user