Source code for app.workers.events
"""ProgressEvent emitter.
The worker writes events to two sinks:
1. `events.jsonl` — durable, used for SSE replay via `Last-Event-ID`.
2. `JobEvent` table — durable index for the API to count/serve events.
Both are appended in order. The `event_id` in the DB doubles as the SSE
event id; clients reconnect with `Last-Event-ID: <int>` to resume from
that point.
"""
from __future__ import annotations
import json
import threading
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
[docs]
class JsonlEventSink:
"""Append-only JSONL sink, thread-safe under a process. Workers run
in subprocess so cross-process locking isn't required for v0."""
def __init__(self, path: Path) -> None:
self.path = path
self.path.parent.mkdir(parents=True, exist_ok=True)
self._lock = threading.Lock()
[docs]
def append(self, payload: dict[str, Any]) -> None:
line = json.dumps(payload, sort_keys=True, separators=(",", ":"))
with self._lock, self.path.open("a", encoding="utf-8") as fh:
fh.write(line + "\n")
fh.flush()
[docs]
def read_from(self, after_seq: int = 0) -> list[dict]:
if not self.path.is_file():
return []
out: list[dict] = []
with self.path.open("r", encoding="utf-8") as fh:
for line in fh:
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except json.JSONDecodeError:
continue
if obj.get("seq", 0) > after_seq:
out.append(obj)
return out
[docs]
def now_iso() -> str:
return datetime.now(UTC).isoformat()