SurgStack exposed two realtime failure modes that executor should make harder to create:
- Websocket query callbacks were allowed to use
Dream.sql requestfrom long-lived realtime request handlers. Dream documents and implements a re-entrant SQL guard because nested or request-bound pool usage can deadlock or wedge request handling. - Database-backed realtime channels were dynamically built from user-facing slugs without an executor-side guardrail for PostgreSQL
LISTENchannel length. PostgreSQL notification channel names are limited to 63 bytes; areport-artifacts-prefix plus a 47-character report slug produced a 64-character channel and raisedFailure("Invalid PostgreSQL channel name: exceeds maximum length (63)").
The application-level mitigation now uses an explicit Caqti pool for realtime snapshots/mutations and caps generated report slugs against the longest realtime channel prefix. The executor DX should still prevent this class of issue at the framework boundary.
- Dream's SQL helper stores an
acquired_sql_connectionLwt key and warns on re-entrantDream.sqlcalls because they can deadlock. - Executor
reason-realtime/dream-middlewaredefaultsuse_dbtoDream.sql. - Executor
server_builder.mlwraps the whole app inDream.sql_poolwhendb_uriis set. - SurgStack originally resolved subscriptions and loaded snapshots with
Dream.sql requestinside websocket callbacks. - Browser behavior alternated between working mutation acks and stuck query updates because mutation and query paths were sharing a fragile request-bound realtime/SQL execution context.
- Realtime logs showed successful mutation receipt followed by missing/stalled query refreshes until the app stopped relying on DB notification patches alone and explicitly broadcast refreshed snapshots for changed report channels.
- A concrete channel safety bug was reproduced:
- Generated slug:
playwright-revenue-review-chromium-178-b1df4904length 47. - Generated channel:
report-artifacts-playwright-revenue-review-chromium-178-b1df4904length 64. - Executor adapter raised:
Invalid PostgreSQL channel name: exceeds maximum length (63).
- Generated slug:
- Realtime runtime now creates its own Caqti pool and passes
~use_dbto middleware instead of usingDream.sql requestfor websocket DB work. - Realtime gateway no longer wraps the websocket server in
Dream.sql_pool. - Successful report mutations now explicitly broadcast refreshed snapshots for affected channels:
reports-demokpis-demoreport-runs-{slug}report-results-{slug}report-artifacts-{slug}
- Report slug generation now caps the slug at
63 - String.length "report-artifacts-", keeping the longest generated PostgreSQL notification channel valid. - Targeted browser verification passed for desktop Chromium and mobile Chrome: create report, reactive report-list update, open detail, run report, render result table/chart, and export CSV.
-
Add a first-class realtime database pool abstraction.
- Middleware should accept DB-backed
resolve_subscriptionandload_snapshotcallbacks that receive a checked-out Caqti connection. - App authors should not need to pass
Dream.requestinto long-lived websocket DB callbacks.
- Middleware should accept DB-backed
-
Make
Dream.sqlusage in websocket callbacks visibly unsafe.- Document this in
reason-realtime/dream-middleware. - Add a runtime warning when middleware is created with default
use_db = Dream.sqland DB-backed snapshots/resolvers are used. - Prefer an executor helper that builds a realtime-safe pool and passes
~use_db.
- Document this in
-
Add channel-name validation utilities before adapter subscription.
- Provide
Channel.make : prefix:string -> id:string -> (string, error) result. - Include the PostgreSQL 63-byte limit in the helper.
- Return a structured client-visible error instead of raising from
Adapter.subscribe.
- Provide
-
Add contract tests that cover mutation plus query refresh together.
- Subscribe to a query.
- Send a mutation that changes that query.
- Assert the mutation ack resolves and the subscribed query receives a fresh snapshot without a page reload.
-
Add a
useQueryskip-transition regression test.- Mount a query with
skip=true. - Flip to
skip=false. - Assert the realtime
selectframe is sent. - SurgStack still has symptoms around later dashboard queries not initializing after gating, so this should be tested at the executor hook level.
- Mount a query with
-
Preserve ping/pong health checks.
- Ping/pong is still present and useful.
- Health checks should not mask cases where the socket is alive but a subscribed query channel is no longer receiving snapshots.
- Middleware unit test: adapter subscription errors are converted to a websocket error frame and do not leave a half-registered in-memory channel.
- Adapter unit test: overlong channel names return structured errors.
- Browser/runtime test: route navigation from
/reportsto dashboard initializes newuseQuerysubscriptions and resolves loading states without a full document reload. - Browser/runtime test: report-style mutation updates a subscribed list and a detail query in the same session.