from __future__ import annotations import json from typing import Any, Protocol from .contracts import KernelTransition, TradeSlot from .control import KernelControlSnapshot from .journal import _transition_row from .projection import build_position_state_row from .utils import json_safe class HazelcastClientLike(Protocol): def get_map(self, name: str): ... def get_topic(self, name: str): ... class HazelcastProjector: """Durable BLUE/PINK-compatible projection mirror.""" def __init__( self, client: HazelcastClientLike | None = None, *, active_slots_map: str = "dita_active_slots", events_topic: str = "dita_trade_events", ) -> None: self.client = client self.active_slots_map = active_slots_map self.events_topic = events_topic def publish_slot(self, slot: TradeSlot) -> None: if self.client is None: return self.client.get_map(self.active_slots_map).put(slot.trade_id, build_position_state_row(slot)) def publish_event(self, event_type: str, payload: dict[str, Any]) -> None: if self.client is None: return topic = self.client.get_topic(self.events_topic) topic.publish( json.dumps( {"event_type": event_type, "payload": json_safe(payload)}, ensure_ascii=False, sort_keys=True, default=str, ) ) class HazelcastRowWriter: """Callback bridge for ``HazelcastProjection`` writer hooks.""" def __init__(self, client: HazelcastClientLike) -> None: self.client = client def __call__(self, name: str, row: dict[str, Any]) -> None: if name.endswith("trade_events"): self.client.get_topic(name).publish( json.dumps(row, ensure_ascii=False, sort_keys=True, default=str) ) return if name.endswith("control"): key = "control" else: key = str(row.get("trade_id", row.get("slot_id", row.get("event_id", "")))) self.client.get_map(name).put(key, json_safe(row))