Files
2026-05-27 23:29:53 +01:00

3.4 KiB

Capability Spec Delta: api-tenant-provider-memory (api-tenant-provider-memory)

Package: @suaveplan/api-tenant-provider-memory Status: PROPOSED

ADDED Requirements

Requirement: Enumerate tenants from in-memory state

The package SHALL implement TenantProvider with list() (sync snapshot), listAsync() (async iterable), and get(tenantId). Initial state comes from options.tenants; runtime mutations are gated by options.mutable.

Scenario: list returns the initial set

  • WHEN the provider is constructed with tenants: [A, B, C]
  • THEN list() returns an array of length 3 containing A, B, C in insertion order

Scenario: async iterator yields every tenant exactly once

  • WHEN for await (const t of provider.listAsync()) runs against an initial set of 3 tenants
  • THEN exactly 3 tenants are yielded AND each ID appears exactly once

Scenario: get returns undefined for unknown tenant

  • WHEN get("unknown") is called
  • THEN the result is undefined (no throw)

Verified by: src/factory/create-memory-tenant-provider.test.ts::"list returns initial set in insertion order" AND src/factory/create-memory-tenant-provider.test.ts::"listAsync yields every tenant exactly once" AND src/factory/create-memory-tenant-provider.test.ts::"get returns undefined for unknown tenant id"


Requirement: Mutation API gated by mutable flag

When options.mutable: true, the provider SHALL expose add(tenant), remove(tenantId), update(tenantId, patch). When mutable: false, those methods MUST throw TENANT_PROVIDER_MEMORY_IMMUTABLE.

Scenario: add rejects duplicate id

  • WHEN add({ id: "acme", ... }) is called twice with the same id
  • THEN the second call throws TENANT_PROVIDER_MEMORY_DUPLICATE_ID

Scenario: remove of unknown tenant throws

  • WHEN remove("not-there") is called
  • THEN the call throws TENANT_PROVIDER_MEMORY_NOT_FOUND

Scenario: mutation blocked when immutable

  • WHEN the provider is constructed with mutable: false AND add(...) is called
  • THEN the call throws TENANT_PROVIDER_MEMORY_IMMUTABLE AND state remains unchanged

Verified by: src/factory/create-memory-tenant-provider.test.ts::"add rejects duplicate id" AND src/factory/create-memory-tenant-provider.test.ts::"remove of unknown tenant throws" AND src/factory/create-memory-tenant-provider.test.ts::"mutation blocked when immutable"


Requirement: Direct and worker-proxy surfaces both pass conformance

The proxy surface SHALL support listAsync() across the MessagePort (chunked yield).

Scenario: direct conforms

  • WHEN runTenantProviderConformance("memory-direct", () => createMemoryTenantProvider(...)) runs
  • THEN every invariant passes

Scenario: worker-proxy conforms with async iteration

  • WHEN runTenantProviderConformance("memory-worker-proxy", ...) runs over a real MessageChannel AND listAsync() is iterated to completion against a 1_000-tenant fixture
  • THEN every invariant passes AND every tenant id is yielded exactly once on the proxy side

Verified by: src/factory/create-memory-tenant-provider.spec-conformance.test.ts::"memory-direct conforms to TenantProvider contract" AND src/proxy/create-memory-tenant-provider-worker-proxy.spec-conformance.test.ts::"memory-worker-proxy conforms with async iteration across MessagePort"