Skip to content

Start design for vehicle positions subscriptions api#572

Draft
irees wants to merge 10 commits intomainfrom
vp-2
Draft

Start design for vehicle positions subscriptions api#572
irees wants to merge 10 commits intomainfrom
vp-2

Conversation

@irees
Copy link
Copy Markdown
Contributor

@irees irees commented Mar 7, 2026

Summary

Add GraphQL subscription support for real-time vehicle position streaming over WebSocket. Clients subscribe to vehicle_positions and receive live GTFS-RT vehicle position snapshots as they arrive, with optional filtering by bounding box and feed. The subscription path reads exclusively from the RT cache (no database), keeping the hot path fast.

See doc/design-vehicle-position-subscriptions.md for the full design document and remaining work items.

GraphQL schema

  • Add Subscription root type with vehicle_positions(where: VehiclePositionFilter) field
  • VehiclePositionFilter supports bbox, feed_onestop_ids, and limit (default/max 1000)
  • Extend VehiclePosition type with trip, feed_onestop_id, bearing, speed fields
  • Change stop_id from Stop (DB-resolved object) to String (raw GTFS-RT value) to keep subscriptions DB-free

WebSocket infrastructure

  • Register WebSocket transport (gorilla/websocket) in gqlgen with 10s keepalive and 30s init timeout
  • Add wsAwareTimeout middleware that bypasses http.TimeoutHandler for WebSocket upgrades while keeping the single router and full middleware stack (no middleware duplication)
  • Add Hijack() on responseWriterWrapper in meters middleware to support WebSocket upgrades
  • Upgrade GraphiQL playground from v0.14 to v3.0.9 with native subscription support

RT cache pub/sub and vehicle position support

  • Add Subscribe() and GetSourceKeys() to the Cache interface
  • LocalCache: in-memory subscriber channels with non-blocking notification
  • RedisCache: Redis PSUBSCRIBE on rtfetch:sub:* channels; removed unused in-memory subscriber dead code
  • Parse vehicle position entities from GTFS-RT feed messages in Source.processMessage
  • Add GetVehiclePositions() and GetCachedFeedIDs() to RTFinder interface and implementation

Subscription resolver

  • Subscribe to cache update notifications, send initial snapshot on connect, push updated snapshots on each RT update
  • convertVehiclePosition: maps protobuf VehiclePosition to GraphQL model (position, bearing, speed, vehicle, trip, stop info, timestamp)
  • Filtering by feed_onestop_ids (at feed level) and bbox (per position)
  • Result capped at limit (default 1000) to prevent unbounded responses

Testing

  • Integration tests using gqlgen WebSocket test client: full field mapping, feed filter, bbox filter (match/no-match), live update flow (empty snapshot → push data → verify update)

Dev tooling

  • cmd/rt-test-server: standalone binary generating synthetic vehicle positions with optional real RT feed fetching
  • testdata/dmfr/rt-test.dmfr.json fixture for local testing

Test plan

  • Run subscription integration tests: go test -run TestSubscription -v ./server/gql/...
  • Start the server with RT feeds configured, open GraphiQL, execute subscription { vehicle_positions { position bearing vehicle { id label } } } and verify WebSocket streaming
  • Verify filtering with where: { bbox: {...}, feed_onestop_ids: [...] } arguments
  • Confirm non-WebSocket HTTP requests still go through the timeout handler normally

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 7, 2026

Generated Code Check ✅

All generated code is up to date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant