Skip to content

fix: Notify overridden flags when the LaunchDarkly data they depend on changes - #2108

Draft
kinyoklion wants to merge 1 commit into
mainfrom
rlamb/overrides-node-ld-update-dependents
Draft

kinyoklion wants to merge 1 commit into
mainfrom
rlamb/overrides-node-ld-update-dependents

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Summary

A flag override can reference LaunchDarkly data: a rule that matches a LaunchDarkly segment, or a prerequisite that is a LaunchDarkly flag. The FDv2 data source updates compute flag change notifications from a dependency tracker built from LaunchDarkly data alone, so a change to such a segment or flag did not notify the flags whose overrides depend on it. The override sink already fans an override change out over the merged view; this is the other direction.

The override layer now keeps the dependency edges of its own entries, rebuilt with the contents on every replacement, and exposes them through the same updateModifiedItems shape the tracker has. TransactionalDataSourceUpdates takes that as an optional external dependency source and, for each modified LaunchDarkly item, expands the affected set through both the LaunchDarkly edges and the override edges until nothing new is reached. A chain can cross in either direction, for example a LaunchDarkly flag whose overridden prerequisite references a LaunchDarkly segment, so one pass through each tracker is not enough. Each tracker gets its own working set because a tracker skips an item that is already in the set it is given.

NamespacedDataSet, which backs DependencyTracker, now uses maps without a prototype. Feeding it the override layer's entries exposed the existing problem that a key named like a property of Object (a flag keyed constructor, __proto__) returned an inherited function from get and made the tracker throw. This is the same hazard the override layer already guards against in its own maps.

Behavior for flags with no overrides is unchanged: with no external dependencies the expansion is the single tracker walk it was before. The FDv1 DataSourceUpdates path is unchanged because overrides exist only with the FDv2 data system.

Spec: OVERRIDE 1.4.2.1 requires the notifications for override changes and reasons that reusing the notification path handles dependent flags the same way as for LaunchDarkly data; this closes the gap where a LaunchDarkly change did not reach override dependents. Raised by Bugbot on the Java (#222) and .NET (#364) stacks; the same change is going into Go and the four other stacks.

Tests

  • __tests__/overrides/OverrideLayer.test.ts: the layer reports the entries that depend on a segment or a prerequisite, transitively through other entries, and forgets edges of entries a replacement removed.
  • __tests__/data_sources/DataSourceUpdates.test.ts: with an override layer as the external dependencies, an upsert of a LaunchDarkly segment notifies the overridden flag that references it; a chain that crosses from a LaunchDarkly flag into an override entry and back to a LaunchDarkly segment notifies both flags; an unrelated item does not notify the override.
  • __tests__/LDClientImpl.overrideDependents.test.ts: an FDv2 polling client with an override whose rule matches a LaunchDarkly segment; after the initial notifications are drained, a poll that changes only the segment notifies only the overridden flag.
  • __tests__/data_sources/NamespacedDataSet.test.ts: entries keyed like properties of Object are stored, found, enumerated, removed, and tracked.

Whole @launchdarkly/js-server-sdk-common suite 1565 total, 7 skipped, all passed; @launchdarkly/node-server-sdk 84 passed; root tsc, oxlint, oxfmt clean.


Note

Overview
Fixes a gap where overridden flags that reference LaunchDarkly segments or prerequisites did not receive change notifications when only the LaunchDarkly side updated.

OverrideLayer now rebuilds a dependency graph for override entries on each snapshot and exposes updateModifiedItems like the main tracker. TransactionalDataSourceUpdates accepts an optional external dependency source (the shared override layer) and, when computing affected items, walks both LaunchDarkly and override edges in a loop so chains can cross layers (e.g. LD flag → overridden prerequisite → LD segment).

LDClientImpl creates one OverrideLayer earlier and passes it into transactional updates as well as the read overlay. NamespacedDataSet uses prototype-less maps so keys such as constructor or __proto__ do not break dependency tracking.

New tests cover layer dependency reporting, transactional updates with overrides, end-to-end FDv2 polling, and NamespacedDataSet edge cases.

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

…n changes

A flag override can reference LaunchDarkly data: a rule that matches a LaunchDarkly segment, or a
prerequisite that is a LaunchDarkly flag. The data system's dependency tracking is built from the
LaunchDarkly data alone, so when such a segment or flag changed, the flags whose overrides depend
on it were not notified through the flag change listeners. The override layer now keeps the
dependency edges of its own entries, rebuilt on every replacement, and the FDv2 data source
updates ask it for the override entries that depend on a changed item. The chain can cross in
both directions, a LaunchDarkly flag with an overridden prerequisite that references a
LaunchDarkly segment for example, so each newly reached item is expanded through both sets of
edges until nothing new is found.

The namespaced data set behind the dependency tracker now uses maps without a prototype, so an
item keyed like a property of Object (constructor, __proto__) is an ordinary entry instead of a
lookup that returns an inherited function and throws.
@kinyoklion

Copy link
Copy Markdown
Member Author

bugbot review

@github-actions

Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 31343 bytes
Compressed size limit: 31500
Uncompressed size: 148858 bytes

@github-actions

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 25998 bytes
Compressed size limit: 44000
Uncompressed size: 167978 bytes

@github-actions

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 34112 bytes
Compressed size limit: 35000
Uncompressed size: 121818 bytes

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b3e6415. Configure here.

This branch has not been deployed

No deployments
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