mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-13 11:42:02 +00:00
feat(multiplayer): wire production proposal outbox delivery
This commit is contained in:
+20
-1
@@ -28,6 +28,13 @@ WHERE published_at IS NULL
|
||||
ORDER BY created_at, event_id
|
||||
LIMIT $1`
|
||||
|
||||
const OutboxUnpublishedProposalSelectSQL = `SELECT event_id, aggregate_type, aggregate_id, revision,
|
||||
event_type, payload, created_at, published_at
|
||||
FROM outbox
|
||||
WHERE published_at IS NULL AND event_type = 'proposal_changed'
|
||||
ORDER BY created_at, event_id
|
||||
LIMIT $1`
|
||||
|
||||
const OutboxMarkPublishedSQL = `UPDATE outbox
|
||||
SET published_at = $2
|
||||
WHERE event_id = $1 AND published_at IS NULL`
|
||||
@@ -88,10 +95,22 @@ func (d *OutboxDispatcher) Dispatch(ctx context.Context, limit int, publishedAt
|
||||
// ReadUnpublishedOutbox returns a bounded, stable ordered batch. It does not
|
||||
// mark rows before delivery: a worker crash therefore leaves events replayable.
|
||||
func ReadUnpublishedOutbox(ctx context.Context, db *sql.DB, limit int) ([]OutboxEvent, error) {
|
||||
return readUnpublishedOutbox(ctx, db, limit, OutboxUnpublishedSelectSQL)
|
||||
}
|
||||
|
||||
// ReadUnpublishedProposalOutbox returns only WebSocket-routable proposal
|
||||
// events. Other outbox consumers (for example result reconciliation) retain
|
||||
// ownership of their event types and cannot be acknowledged accidentally by
|
||||
// the control-plane WebSocket dispatcher.
|
||||
func ReadUnpublishedProposalOutbox(ctx context.Context, db *sql.DB, limit int) ([]OutboxEvent, error) {
|
||||
return readUnpublishedOutbox(ctx, db, limit, OutboxUnpublishedProposalSelectSQL)
|
||||
}
|
||||
|
||||
func readUnpublishedOutbox(ctx context.Context, db *sql.DB, limit int, query string) ([]OutboxEvent, error) {
|
||||
if db == nil || limit < 1 || limit > 1000 {
|
||||
return nil, fmt.Errorf("invalid outbox read arguments")
|
||||
}
|
||||
rows, err := db.QueryContext(ctx, OutboxUnpublishedSelectSQL, limit)
|
||||
rows, err := db.QueryContext(ctx, query, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ import (
|
||||
|
||||
func TestOutboxSQLPreservesReplayableOrderedReadAndPublishAck(t *testing.T) {
|
||||
for query, fragments := range map[string][]string{
|
||||
OutboxUnpublishedSelectSQL: {"published_at IS NULL", "ORDER BY created_at, event_id", "LIMIT $1"},
|
||||
OutboxMarkPublishedSQL: {"published_at = $2", "event_id = $1", "published_at IS NULL"},
|
||||
OutboxUnpublishedSelectSQL: {"published_at IS NULL", "ORDER BY created_at, event_id", "LIMIT $1"},
|
||||
OutboxUnpublishedProposalSelectSQL: {"published_at IS NULL", "event_type = 'proposal_changed'", "ORDER BY created_at, event_id", "LIMIT $1"},
|
||||
OutboxMarkPublishedSQL: {"published_at = $2", "event_id = $1", "published_at IS NULL"},
|
||||
} {
|
||||
for _, fragment := range fragments {
|
||||
if !contains(query, fragment) {
|
||||
|
||||
Reference in New Issue
Block a user