Skip to content

feat(sse): Server-Sent Events directive — release v1.20.0#292

Merged
ErickXavier merged 22 commits into
mainfrom
feat/NOJS-270
Jul 17, 2026
Merged

feat(sse): Server-Sent Events directive — release v1.20.0#292
ErickXavier merged 22 commits into
mainfrom
feat/NOJS-270

Conversation

@ErickXavier

Copy link
Copy Markdown
Collaborator

Summary

New sse directive: declarative Server-Sent Events / EventSource support — the streaming counterpart to get. Full initiative (EPIC NOJS-270): implementation, unit + E2E suites, markdown/HTML/i18n (5 locales)/LLM docs, ecosystem sync, version bump to 1.20.0, CHANGELOG.

Directive surface

sse="url" ({expr} interpolation, reactive reconnect), as (default "data"), sse-event, sse-insert="replace|append|prepend", sse-limit, sse-credentials, into (store dual-write), error="#tplId" (terminal CLOSED only), then (per-message), $sse status context, per-origin connection warning.

Task PRs merged into this branch (each QA-gated with /code-review)

Verification

  • Unit: 45 suites, 2431 passed / 3 skipped
  • E2E: cross-browser SSE specs (Chromium/Firefox/WebKit) + axe accessibility
  • dist/ rebuilt and byte-identical to source at every gate; grep -c "1.20.0" dist/iife/no.js = 2
  • Security review of full executable diff: no HIGH/MEDIUM findings

Release plan (after this PR)

Merge held satellite PRs (nojs-lsp#80+#81 — second resolves out/ by rebuild; nojs-elements#74; nojs-skill#69+#70), then tag v1.20.0 on all repos. CDN updates via jsDelivr from the tag; LSP publishes via GitHub Actions.

ErickXavier and others added 22 commits July 15, 2026 13:44
NOJS-270 / NOJS-271

New directive `sse` enables declarative real-time data binding via the
browser-native EventSource API. Registered at priority 1 (same tier as
get/post), gated for if-conditional support.

Attribute API: sse (url), as (var name), sse-event (named event),
sse-insert (replace|append|prepend), sse-limit (array cap),
sse-credentials (withCredentials), into (store dual-write),
error (template on terminal close), then (per-message expression).

Reactive $sse state object ({connecting, open, error}) exposed on the
element context via a new switch case in context.js. Per-origin
connection tracking warns at the HTTP/1.1 6-connection limit. Reactive
URL interpolation follows the ancestor-watch pattern from http.js.

All mandatory safety rules enforced: disposal via _onDispose,
el.isConnected guards, watcher cleanup, dispose-before-innerHTML.

Bundle delta: +2,814 bytes (2.75 KB), within 3 KB budget.
…date sse-limit

S1: Move _onDispose(_closeConnection) from inside _openConnection (where
_currentEl may be null during reactive watcher callbacks) to the synchronous
directive init path. The closure-based _closeConnection always closes
whatever the current EventSource is, so a single registration covers all
reconnect scenarios.

N1: Add _log() debug output for SSE lifecycle events (connect, open,
message, named-event, close, error, reconnect, URL change) consistent
with the project's logging conventions.

N2: Warn via _warn() when sse-limit is negative or non-numeric instead
of silently ignoring the invalid value.
fix(sse): register disposal once during init, add debug logging, validate sse-limit
Add 4 real SSE streaming endpoints to test-server.js (/sse/messages,
/sse/error, /sse/infinite, /sse/credentials) and 8 cross-browser
Playwright specs covering live binding, append with limit, named events,
$sse state transitions, error template on terminal close, store
integration via into, disposal on if-toggle, and axe accessibility.
test(sse): add unit tests with MockEventSource (NOJS-272)
- New docs/md/sse.md with full attribute reference, connection state
  lifecycle, insert modes, authentication limitations, HTTP/1.1
  connection limit guidance, and SSE vs polling comparison
- README.md: add SSE to feature list and documentation table
- docs/md/data-fetching.md: add cross-reference to SSE guide
Address QA review findings on PR #286:

- HIGH: add /sse/stats endpoint (global + per-channel via ?chan=) tracking
  open SSE connections server-side; Test 7 now polls the count to 0 after
  disposal and asserts the stream restarts at Tick 1 on re-toggle, so a
  leaked EventSource (the #285 bug class) can no longer pass
- MEDIUM: cover /sse/credentials with a fixture section + spec asserting
  cookie auth observable server-side (401 without cookie, 200 with)
- MEDIUM: add Test 9 regression for the #285 path — interpolated sse URL,
  state change forces reconnect, dispose section, poll /sse/stats to 0
- LOW: Test 4 now asserts the connecting state deterministically via a
  server-side ?start=800 header delay before asserting open
- LOW: /sse/error sends retry: 100 so terminal-close reconnect timing no
  longer depends on the browser default retry delay

Also fix a Firefox flake: fixture sections get a fixed height so streaming
content cannot shift the toggle buttons between Playwright's actionability
check and the click dispatch. Channels use a per-page-load random id so
parallel workers never share /sse/stats counters.
Add complete documentation for the sse directive:
- New docs/templates/docs/sse.tpl with 13 sections covering all attributes,
  insert modes, named events, $sse state, reactive URLs, store integration,
  error handling, then callback, auth limitations, and connection limits
- Sidebar navigation entry in docs/templates/docs/sidebar.tpl
- i18n keys in all 5 locales (en, pt, es, fr, it): 47 keys in docs.sse
  section, 5 keys in cheatsheet.data section, 1 sidebar key in shell.json
- LLM summary in docs/llms.txt and full section in docs/llms-full.txt
  with cheatsheet entries
- Fix invalid computed syntax: computed="name" expr="..." (F1)
- Fix bare hide attribute: hide="true" (F2)
- Remove false connection-sharing claim; connections are never coalesced (F3)
- Wire SSE into prev/next nav chain across data-fetching, sse, data-binding (F4)
- Add sse-limit to canonical append/prepend examples (F5)
- Add encodeURIComponent note to reactive URL section (F6)
- Correct overstated replace-mode warning: only non-zero limit warns (F7)
- Add sse-limit constraint details to attributes table (F8)
- Initialize store ticker to {} instead of null to avoid null-deref (F9)
test(e2e): add SSE directive specs and streaming endpoints
- N1: Replace localStorage.getItem() in computed expr (blocked by
  _BLOCKED_WINDOW_PROPS) with <script>-seeded store pattern using
  NoJS.config({ stores }) and {$store.auth.token} interpolation
- N2: Remove location.reload() retry buttons (location resolves to
  _safeLocation with reload = _locationNoop); add note explaining
  the evaluator constraint and app-level JS escape hatch
- N3: Add # prefix to error template IDs (error="#sseFailed",
  error="#feedError") matching convention in all other docs
docs(sse): add SSE directive markdown documentation
…et rows

- Fix blocker: standalone t-html="key" -> t="key" t-html (sse.tpl:201)
- Fix reactiveUrlText: as variable reset only in append/prepend mode,
  not in replace mode (5 locales + llms-full.txt)
- Document both _warn calls: append/prepend without sse-limit warns
  about unbounded memory; sse-limit in replace mode warns no effect
  (insertModesText + sseLimit in 5 locales + llms-full.txt)
- Fix errorHandlingText: template receives synthetic { message: "SSE
  connection closed" }, not the browser error object (5 locales +
  llms-full.txt)
- Fix connectionLimitText: connections are never shared/coalesced;
  remove misleading named-events consolidation advice, recommend
  HTTP/2, fewer elements, or conditional rendering (5 locales +
  llms-full.txt)
- Wire 5 dead cheatsheet.data.sse* keys into cheatsheet.tpl after
  retryDelay row
- Fix Data Binding Prev/Next footer in llms-full.txt (Previous now
  points to SSE instead of Data Fetching)
- Fix header comment convention in sse.tpl (add "from sse.md" suffix)
docs(sse): HTML docs, i18n, and LLM docs for SSE directive
chore(release): bump version to 1.20.0
@ErickXavier
ErickXavier merged commit 34b4236 into main Jul 17, 2026
1 check passed
@ErickXavier
ErickXavier deleted the feat/NOJS-270 branch July 17, 2026 12:30
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