Files

42 lines
1.4 KiB
Python
Raw Permalink Normal View History

2026-09-09 21:55:37 +08:00
from datetime import UTC, datetime
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.model.memory import MemorySyncOutbox
class ProjectionReconciliationService:
"""Finds projection events that still need delivery or replay."""
def __init__(self, session: AsyncSession) -> None:
self.session = session
async def pending(self, *, limit: int = 100) -> list[MemorySyncOutbox]:
now = datetime.now(UTC).replace(tzinfo=None)
result = await self.session.scalars(
select(MemorySyncOutbox)
.where(
MemorySyncOutbox.status.in_({"pending", "failed"}),
(
MemorySyncOutbox.next_retry_at.is_(None)
| (MemorySyncOutbox.next_retry_at <= now)
),
)
.order_by(MemorySyncOutbox.id)
.limit(max(1, min(limit, 1000)))
)
return list(result)
async def mark_replay(self, event_uuid: str) -> bool:
event = await self.session.scalar(
select(MemorySyncOutbox).where(MemorySyncOutbox.event_uuid == event_uuid)
)
if event is None or event.status == "processed":
return False
event.status = "pending"
event.next_retry_at = None
event.last_error = None
await self.session.commit()
return True