Skip to content

feat: [FD-5562] Adding FDv2 Stream endpoint support in the dev server#703

Open
hkotian wants to merge 3 commits intomainfrom
hkotian/FD-5562/v2-stream-support
Open

feat: [FD-5562] Adding FDv2 Stream endpoint support in the dev server#703
hkotian wants to merge 3 commits intomainfrom
hkotian/FD-5562/v2-stream-support

Conversation

@hkotian
Copy link
Copy Markdown
Contributor

@hkotian hkotian commented Apr 29, 2026

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

Provide links to any issues in this repository or elsewhere relating to this pull request.

Describe the solution you've provided

Piggy backing from the polling fdv2 support, in this PR we are introducing the FDv2 streaming endpoint support.
Like polling endpoints, the initial transfer would either be

  1. No-transfer: If there is no change in the payload
  2. Or full transfer: Otherwise

An override or sync would also send stream updated in the FDv2 format, as expected.

On a sync the updates will look as follows

event:server-intent
data:{"payloads":[{"id":"fw-test-project","target":15,"intentCode":"xfer-full","reason":"cant-catchup"}]}

event:put-object
data:{"version":15,"kind":"flag","key":"update-flag-4","object":{"key":"update-flag-4","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":107,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"test-flag-22","object":{"key":"test-flag-22","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":6,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"flag-2","object":{"key":"flag-2","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":["B"],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":2,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"flag-A","object":{"key":"flag-A","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":["B"],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":7,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"bool-flag","object":{"key":"bool-flag","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":10,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"pre-req","object":{"key":"pre-req","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":2,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"update-flag-0","object":{"key":"update-flag-0","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":107,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"update-flag-1","object":{"key":"update-flag-1","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":107,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"pre-req-with-string","object":{"key":"pre-req-with-string","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":["second"],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":2,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"update-flag-3","object":{"key":"update-flag-3","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":107,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"update-flag-2","object":{"key":"update-flag-2","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[false],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":107,"deleted":false}}

event:put-object
data:{"version":15,"kind":"flag","key":"flag-3","object":{"key":"flag-3","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":["B"],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":false,"trackEventsFallthrough":false,"debugEventsUntilDate":0,"version":2,"deleted":false}}

event:payload-transferred
data:{"state":"(p:fw-test-project:15)","version":15}

Whereas a single override will emit a single event :

event:server-intent
data:{"payloads":[{"id":"fw-test-project","target":16,"intentCode":"xfer-changes","reason":"update"}]}

event:put-object
data:{"version":16,"kind":"flag","key":"bool-flag","object":{"key":"bool-flag","on":true,"prerequisites":[],"targets":[],"rules":[],"fallthrough":{"variation":0},"offVariation":0,"variations":[true],"clientSideAvailability":{"usingMobileKey":true,"usingEnvironmentId":true},"clientSide":true,"salt":"","trackEvents":true,"trackEventsFallthrough":true,"debugEventsUntilDate":0,"version":11,"deleted":false}}

event:payload-transferred
data:{"state":"(p:fw-test-project:16)","version":16}

Describe alternatives you've considered

Provide a clear and concise description of any alternative solutions or features you've considered.

Additional context

Add any other context about the pull request here.


Note

Medium Risk
Touches core streaming infrastructure and event payload shapes; mistakes could break SSE clients or cause versioning inconsistencies across polling/streaming updates.

Overview
Adds a new /sdk/stream endpoint that serves Flag Delivery v2 over SSE: it sends an initial FDv2 response based on basis, then pushes incremental flag updates (xfer-changes + put-object + payload-transferred) on overrides and full retransfers on project sync.

To support streaming updates, OverrideEvent and SyncEvent now carry PayloadVersion (propagated from IncrementProjectPayloadVersion), and the generic SSE plumbing is refactored so OpenStream/observers write raw []byte payloads (with Message.ToPayload() used by non-FDv2 streams).

Reviewed by Cursor Bugbot for commit 3b1c960. Bugbot is set up for automated code reviews on this repo. Configure here.


Related Jira issue: FD-5562: Implement server side streaming endpoint handling in Launchdarkly dev server

@launchdarkly-upra launchdarkly-upra Bot changed the title Adding FDv2 Stream endpoint support in the dev server [FD-5562] Adding FDv2 Stream endpoint support in the dev server Apr 29, 2026
@hkotian hkotian changed the title [FD-5562] Adding FDv2 Stream endpoint support in the dev server [FD-5562] feat: Adding FDv2 Stream endpoint support in the dev server Apr 30, 2026
@hkotian hkotian changed the title [FD-5562] feat: Adding FDv2 Stream endpoint support in the dev server feat: [FD-5562] Adding FDv2 Stream endpoint support in the dev server Apr 30, 2026
@hkotian hkotian marked this pull request as ready for review April 30, 2026 02:23
@hkotian hkotian requested review from a team, colincstuart, cspath1, keelerm84, kinyoklion, nieblara, singhshalinis01 and svenkatesan-ld and removed request for a team April 30, 2026 02:23
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1f4132f. Configure here.

Comment thread internal/dev_server/sdk/stream_server_fdv2.go Outdated
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