"""Channel connector registry.""" from __future__ import annotations from typing import Protocol from beaver.foundation.config.schema import ChannelConfig from .models import ChannelRuntimeSpec, ValidationResult from .store import ChannelConnectionStore, CredentialStore class ChannelConnector(Protocol): kind: str async def validate(self, connection_id: str) -> ValidationResult: ... async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec: ... async def revoke(self, connection_id: str) -> None: ... class ChannelConnectorRegistry: def __init__(self, *, connection_store: ChannelConnectionStore, credential_store: CredentialStore) -> None: self.connection_store = connection_store self.credential_store = credential_store self._connectors: dict[str, ChannelConnector] = {} def register(self, connector: ChannelConnector) -> None: kind = connector.kind.strip() if not kind: raise ValueError("Connector kind is required") if kind in self._connectors: raise ValueError(f"Connector already registered: {kind}") self._connectors[kind] = connector def connectors(self) -> list[dict[str, str]]: return [{"kind": kind} for kind in sorted(self._connectors)] def connector_for_kind(self, kind: str) -> ChannelConnector: return self._connector(kind) async def validate(self, connection_id: str) -> ValidationResult: connection = self.connection_store.get(connection_id) connector = self._connector(connection.kind) result = await connector.validate(connection_id) self.connection_store.update_status( connection_id, status=result.status, last_error=result.error, ) return result async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec: connection = self.connection_store.get(connection_id) return await self._connector(connection.kind).materialize_runtime(connection_id) async def materialize_connected_runtime_specs(self) -> list[ChannelRuntimeSpec]: specs: list[ChannelRuntimeSpec] = [] for connection in self.connection_store.list(): if connection.status not in {"connected", "running"}: continue specs.append(await self._connector(connection.kind).materialize_runtime(connection.connection_id)) return specs async def materialize_channel_configs(self) -> dict[str, ChannelConfig]: channels: dict[str, ChannelConfig] = {} for spec in await self.materialize_connected_runtime_specs(): secrets = self.credential_store.get(spec.secrets_ref) if spec.secrets_ref else {} channels[spec.channel_id] = ChannelConfig( enabled=True, kind=spec.kind, mode=spec.mode, account_id=spec.account_id, display_name=spec.display_name, config=dict(spec.config), secrets=secrets, ) return channels async def revoke(self, connection_id: str) -> None: connection = self.connection_store.get(connection_id) await self._connector(connection.kind).revoke(connection_id) self.connection_store.revoke(connection_id) def _connector(self, kind: str) -> ChannelConnector: connector = self._connectors.get(kind) if connector is None: raise KeyError(f"Connector not registered: {kind}") return connector