*/ public array $documents = []; public ?string $lockOwner = null; /** Fixed time for listedAt; null = now */ public ?int $clock = null; public function upsert(string $tenantId, string $serviceId, CollectionResource $collection): void { $name = (string) $collection->identifier(); $this->documents[$name] = [ ...($this->documents[$name] ?? self::STATE_DEFAULTS), ...$collection->toCacheMeta(), 'listedAt' => $this->clock ?? time(), ]; } public function fetch(string $serviceId, string $name): ?array { return $this->documents[$name] ?? null; } public function list(string $serviceId): array { return $this->documents; } public function updateState(string $serviceId, string $name, array $state): void { $this->documents[$name] = array_replace($this->documents[$name], array_intersect_key($state, self::STATE_DEFAULTS)); } public function acquireLock(string $serviceId, string $name, string $owner, int $ttl): bool { if ($this->lockOwner !== null && $this->lockOwner !== $owner) { return false; } $this->lockOwner = $owner; return true; } public function releaseLock(string $serviceId, string $name, string $owner): void { if ($this->lockOwner === $owner) { $this->lockOwner = null; } } public function reserveSequence(string $serviceId, string $name, int $count = 1): int { $this->documents[$name]['changeSeq'] += $count; return $this->documents[$name]['changeSeq'] - $count + 1; } public function state(string $serviceId, string $name): array { return array_replace(self::STATE_DEFAULTS, array_intersect_key($this->documents[$name] ?? [], self::STATE_DEFAULTS)); } public function listedAt(string $serviceId): ?int { $times = array_filter(array_column($this->documents, 'listedAt')); return $times === [] ? null : max($times); } public function delete(string $serviceId, string $name): void { unset($this->documents[$name]); } }