feat(surrealdb-ractor): implement LiveQueryRouter::run + live_stream (Sprint 1 follow-up to #24)#25
Conversation
…(Sprint 1)
Wave-5 worker W-SD-5. Replaces wave-1 `unimplemented!()` stubs with
real bodies using SurrealDB 3.1.0-alpha's live-query subscription API.
* surrealdb-ractor/src/stream.rs — `live_stream(query) -> impl Stream<Item = Result<LiveDelta>>`
uses `db.query("LIVE SELECT * FROM table").stream::<Value>(0)`
(surrealdb/src/method/query.rs:394). The idiomatic
`db.select::<Vec<T>>(table).live()` shape from the plan sketch hit a
trait-bound wall — `&str` satisfies `IntoResource<Vec<R>>` but not
`IntoResource<Value>`, and `surrealdb_types::Value` doesn't implement
`SurrealValue` required for `Notification<R>`. The query() path
accepts any SQL string and returns `QueryStream<Value>`.
* surrealdb-ractor/src/router.rs — `LiveQueryRouter<M>::run` loops over
the stream, maps Notification → LiveDelta via Action enum
(Create/Update/Delete/Killed at surrealdb/types/src/notification.rs:18),
and forwards to the configured actor via `M::from(delta)`.
* surrealdb-ractor/src/delta.rs — minor revisions to support the Value
payload form (Arrow conversion deferred to Sprint 2; current impl
carries serde_json::Value behind the variant).
* surrealdb-ractor/Cargo.toml — added serde (with derive) + tracing.
* surrealdb-ractor/tests/live_query.rs — e2e test: spin up in-memory
surrealdb with `Capabilities::all()` (live-query notifications are
gated on the local engine), DEFINE TABLE events SCHEMALESS, start
router watching SELECT * FROM events, INSERT 3 records, assert
counter sees 3 Create events. Passes in 1.29s.
Real-world deviations from plan sketch (documented inline):
1. `select().live()` not usable for Value payloads → query() path.
2. 3.1.0-alpha requires DEFINE TABLE before LIVE SELECT.
3. In-memory engine requires Capabilities::all() for notifications.
Verdict: LAND. cargo check clean, 1/1 e2e test passes.
Worker: W-SD-5. Sprint 1 of the four-repo integration.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a485f1a37
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // | ||
| // SDK path: surrealdb/src/method/query.rs:394 (IndexedResults::stream) | ||
| // surrealdb/src/method/query.rs:164 (live-query UUID extraction) | ||
| let sql = format!("LIVE SELECT * FROM {table}"); |
There was a problem hiding this comment.
Accept query strings instead of treating them as table names
When callers use LiveQueryRouter::query as the SurrealQL query string shown in the integration plan (for example, SELECT * FROM events WHERE category = 'sale' or a full LIVE SELECT ...), this unconditional wrapping produces invalid SQL like LIVE SELECT * FROM SELECT * FROM events ... and the router fails before subscribing. Either pass through full query strings or rename/restrict the API to validated table identifiers so documented filtered subscriptions keep working.
Useful? React with 👍 / 👎.
Summary
Follow-up to merged PR #24 (Sprint 0/1 surrealdb-ractor scaffolds). The wave-5 W-SD-5 commit
74d4afdwas pushed to the branch after #24 merged, so the real Sprint-1 implementation ofLiveQueryRoutergot stranded. This PR brings it onto main.What lands
Replaces the wave-1
unimplemented!()stubs insurrealdb-ractorwith real bodies using SurrealDB 3.1.0-alpha's live-query subscription API:surrealdb-ractor/src/stream.rs—live_stream(query) -> impl Stream<Item = Result<LiveDelta>>usesdb.query("LIVE SELECT * FROM table").stream::<Value>(0)(surrealdb/src/method/query.rs:394). The idiomaticdb.select::<Vec<T>>(table).live()shape from the plan sketch hit a trait-bound wall —&strsatisfiesIntoResource<Vec<R>>but notIntoResource<Value>, andsurrealdb_types::Valuedoesn't implementSurrealValuerequired forNotification<R>. Thequery()path accepts any SQL string and returnsQueryStream<Value>.surrealdb-ractor/src/router.rs—LiveQueryRouter<M>::runloops over the stream, mapsNotification→LiveDeltavia theActionenum (Create/Update/Delete/Killed atsurrealdb/types/src/notification.rs:18), and forwards to the configured actor viaM::from(delta).surrealdb-ractor/src/delta.rs— minor revisions to support theValuepayload form (Arrow conversion deferred to Sprint 2; current impl carriesserde_json::Valuebehind the variant).surrealdb-ractor/Cargo.toml— addedserde(with derive) +tracing.surrealdb-ractor/tests/live_query.rs(new) — e2e test: spin up in-memory surrealdb withCapabilities::all()(live-query notifications are gated on the local engine),DEFINE TABLE events SCHEMALESS, start router watchingSELECT * FROM events,INSERT3 records, assert counter sees 3 Create events. Passes in 1.29s.Real-world deviations from the plan sketch (documented inline)
db.select::<Vec<T>>(table).live()not usable forValuepayloads →query()path used instead.DEFINE TABLE ... SCHEMALESSbeforeLIVE SELECT.Capabilities::all()for notifications to fire.Test plan
cargo check --manifest-path surrealdb-ractor/Cargo.toml— cleancargo test --manifest-path surrealdb-ractor/Cargo.toml --test live_query— 1/1 passes in 1.29sValuepayload to a proper ArrowRecordBatchso consumers downstream of sea-orm-arrow can stay in columnar form (out of scope)EntityActordispatch glue sodelta.primary_key("id")routes directly toTicket::Entity::actor(pk)(cross-repo: sea-orm-ractor side)Context
claude/lance-surrealdb-analysis-LXmug(commit74d4afd).Worker: W-SD-5. Sprint 1 of the four-repo integration.
https://claude.ai/code/session_01LiUiGeUDLje8KMnxB4FfA3
Generated by Claude Code