Skip to content

fix(api-websockets): deliver server->client pushes on SQL deployments (recency filter) - #5553

Open
adrians5j wants to merge 10 commits into
nextfrom
adrian/self-hosted-watch-message
Open

fix(api-websockets): deliver server->client pushes on SQL deployments (recency filter)#5553
adrians5j wants to merge 10 commits into
nextfrom
adrian/self-hosted-watch-message

Conversation

@adrians5j

@adrians5j adrians5j commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

On SQL-backed (self-hosted / server flavour) deployments, SendToIdentity silently matched zero connections, so server→client WebSocket pushes never arrived — even though the connection was registered under the correct identity. Symptom: file-manager AI image enrichment completes and updates the file, but no "Image enriched" notification appears in Admin.

Root cause

connectedOn is declared string and written as a UTC ISO string, but it lives in a SQL datetime column. On read the driver hands it back in a shape that violates the declared type: a Date (node-postgres / some sqlite clients) or a T/Z-less "2026-08-04 17:02:06". ListConnectionsUseCase then filtered stale connections with a lexicographic string compare against an ISO cutoff — a Date coerces to "Wed Aug 04 2026 …" (sorts below "2026-…"), and the space form's space (0x20) sorts before T (0x54). Either way every live connection read as expired and was dropped. SendToIdentity sent to nobody, threw nothing, and the task still reported success.

The type violation was wider than the filter: the SQL registry's toData() passed the raw driver value straight through, so it also reached WebsocketsGraphQLFactory's connectedOn: DateTime! field, and a unit test asserting expect.any(String) failed on the PGlite lane.

Fix — normalize at the SQL boundary

Normalize connectedOn to a canonical UTC ISO string once, in WebsocketsConnectionRegistry.toData() (@webiny/api-websockets-sql) — a Date maps through toISOString(), the space form is read as UTC (safe: the stored wall-clock is UTC). Every consumer now gets the declared string. With that guarantee, ListConnectionsUseCase goes back to a plain ISO string comparison (ISO-8601 UTC strings sort chronologically) — no per-consumer date parsing.

Verification

  • yarn test:pglite packages/api-websockets — 25/25 pass (was 1 failing: "should properly list connections")
  • yarn test packages/api-websockets (ddb) — 25/25 pass
  • yarn test:pglite packages/api-websockets-sql — 10/10 pass
  • Manually reproduced end-to-end on a self-hosted SQL project: upload image → AI enrichment task → notification renders in Admin.

Not a WS-wiring regression

The recent 6.5.0 merges did not revert any WebSocket source; the wiring (register → sql registry → SendToIdentity → transport → admin handler) is intact. Latent bug in a new code path. AWS/DynamoDB never hit it — ddb stores connectedOn as a plain string attribute that round-trips as ISO.

Follow-up (out of scope here) — recency filter keys off the wrong field

ListConnectionsUseCase judges "recent" by connectedOn (first-connect time), but listStale correctly uses lastSeen (heartbeat). So an Admin session open > 3 hours — alive and heartbeating, lastSeen fresh — has an old connectedOn and gets filtered out, silently losing pushes. The heartbeat/lastSeen machinery exists for exactly this. Worth a separate PR: filter on lastSeen (fall back to connectedOn when never heard from). Independent of the type-normalization fixed here.

🤖 Generated with Claude Code

adrians5j and others added 8 commits August 4, 2026 12:56
Watching all apps at once isn't supported yet for the self-hosted hosting
type, but the post-create message told users to run a single `webiny watch`
command. Update the message to run the API and Admin apps separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…placeholder

oxfmt reformatted the admin index.tsx `{GLOBAL_CSS}` placeholder into a block
statement, so the literal find-replace in ServerBuildAppWorkspaceService no
longer matched and the bare `GLOBAL_CSS;` shipped to the workspace (TS2304).
Exclude the server template's appTemplates folder from oxfmt (mirroring the
AWS template) and restore the placeholder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…config

The 6.5.0 stale-branch merge (#5396) clobbered createRsbuildConfig.js back to a
pre-#5453 state, dropping the `assetPrefix: "auto"` config and the isServer
externals gating. Without assetPrefix, the self-hosted bg-tasks worker chunk
(spawned via `new Worker(new URL(..., import.meta.url))`) resolved to an
absolute filesystem-root URL and failed with "Cannot find module". The
referencing comments in WorkerTaskService/BreeSchedulerService survived the
revert, masking the loss. Restore the #5453 version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SendToIdentity silently matched zero connections on SQL-backed (self-hosted)
deployments, so server->client pushes (e.g. the file-manager AI enrichment
notification) never arrived even though the connection was registered under the
correct identity.

ListConnectionsUseCase filtered stale connections with a lexicographic string
comparison (`connectedOn >= <iso cutoff>`). But `connectedOn` is a SQL
`datetime` column, and the driver returns it in a shape that doesn't compare
against an ISO string: mysql2 hands back a `Date` (coerces to "Wed Aug 04
2026 ...", sorts below "2026-...") and other drivers return a `T`/`Z`-less
"2026-08-04 17:02:06" (space sorts before `T`). Either way every live
connection read as expired and was dropped.

Compare by parsed UTC epoch instead, normalizing the space form back to UTC
(safe — the stored wall-clock is UTC), so it works regardless of driver format.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the connectedOn epoch parsing and recency predicate out of execute() into
module-level helpers (connectedOnToEpoch, isRecentConnection) plus a named
RECENT_CONNECTION_WINDOW_MS constant. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

🚓 Slop Cop

✅ Nothing worth flagging. The diff looks consistent with the PR's stated intent and the code-style rules.

The PR is a small, coherent fix matching its stated intent (normalizing connectedOn at the SQL boundary and comparing ISO strings), with no integrity red flags and no clear style-rule violations in the added lines.

Automated, non-blocking heads-up from an LLM. It can be wrong — use your judgment. Regenerates on every push.

@adrians5j

Copy link
Copy Markdown
Member Author

/e2e

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Cypress E2E tests have been initiated (for more information, click here). ✨

Database Status Admin URL
DDB ✅ Ready https://d3dlfil2f50xgp.cloudfront.net
DDB+OS ✅ Ready https://d3c8y6mnx6yxwr.cloudfront.net

@adrians5j

Copy link
Copy Markdown
Member Author

/vitest

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Vitest tests have been initiated (for more information, click here). ✨

Group Status
No storage ✅ 59/59 passed
DDB ✅ 17/17 passed
DDB+OS ✅ 19/19 passed
SQL ✅ 10/10 passed
PGlite ❌ 9/10 passed
❌ Failed packages

PGlite

  • api-websockets

…ndary

Root-cause fix for server->client pushes matching zero connections on SQL
deployments. `connectedOn` is declared `string` and written as a UTC ISO
string, but the `datetime` column is read back driver-specifically — a `Date`
(node-postgres, some sqlite clients) or a `T`/`Z`-less "2026-08-04 17:02:06".
That violated the type everywhere it flows (the recency filter dropped every
connection; the GraphQL `connectedOn: DateTime!` field could serialize
inconsistently; a test asserting `expect.any(String)` failed on PGlite).

Normalize the value to a canonical UTC ISO string once, in the SQL registry's
`toData()` boundary, so every consumer gets the declared `string`. With that
guarantee, ListConnectionsUseCase goes back to a plain ISO string comparison
(ISO-8601 UTC strings sort chronologically) — no per-consumer parsing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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