feat: Add the override store, overlay, and data system wiring - #2051
kinyoklion wants to merge 4 commits into
Conversation
9e22472 to
e6ad1d1
Compare
9bc7846 to
0c00688
Compare
e6ad1d1 to
30eb906
Compare
0c00688 to
e42e2cb
Compare
30eb906 to
11aaea9
Compare
e42e2cb to
8e9fd05
Compare
|
@launchdarkly/js-client-sdk-common size report |
|
@launchdarkly/js-sdk-common size report |
|
@launchdarkly/js-client-sdk size report |
11aaea9 to
91d45a2
Compare
8e9fd05 to
71a1426
Compare
91d45a2 to
7874143
Compare
71a1426 to
a9e83b1
Compare
7874143 to
4ecf628
Compare
a9e83b1 to
a5614f5
Compare
4ecf628 to
2be4e36
Compare
a5614f5 to
d0ed461
Compare
2be4e36 to
fa5493d
Compare
d0ed461 to
667c330
Compare
fa5493d to
f8012e2
Compare
667c330 to
431c72b
Compare
f8012e2 to
5605b28
Compare
431c72b to
980675a
Compare
5605b28 to
f5a88e9
Compare
980675a to
d3694fb
Compare
f5a88e9 to
08abea8
Compare
d3694fb to
d1d703d
Compare
5605b28 to
08abea8
Compare
Adds the override layer described by the OVERRIDE specification to the FDv2 data system of the server SDK. An override source is configured with the new overrides option of the data system options. It is not a data source: it takes no part in the initializer and synchronizer pipeline and has no effect on initialization status. The SDK starts it when the client is created, passes it a sink, and closes it with the client. The public LDOverrideSource and LDOverrideSink interfaces let an application supply a custom source. The source supplies complete snapshots to the override store. Each snapshot replaces the whole store in one assignment. The store holds prepared, marked copies of the definitions and never modifies the objects the source supplied. An overlay at the store read boundary returns the override entry for a key in preference to LaunchDarkly data. Evaluation, prerequisite and segment resolution, and the all-flags read go through the overlay. The client consults the override store before the not-initialized short circuit, so an overridden flag is served before the client has LaunchDarkly data while a non-overridden flag still gets the not-ready default. Evaluations wait for the source's asynchronous initial load, so an override present at startup takes effect from the first evaluation. The all-flags state keeps override-affected flags with their values and marked reasons but turns their event tracking fields off. A type mismatch keeps the marking of the evaluation it replaces. Adding, changing, or removing an override fires the normal flag change notifications, including for the flags that depend on a changed entry. The OVERRIDE specification's test vectors run as a unit test through the full client stack.
…the override layer A throwing override source close is logged and the rest of the shutdown continues. Before initialization, the all flags state uses the override layer only when it holds at least one flag.
08abea8 to
4b7069e
Compare
557ef18 to
4fc2262
Compare
|
bugbot review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ 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 4fc2262. Configure here.
| this._logger?.debug(`Override update affected ${flagKeys.length} flag(s)`); | ||
| } | ||
| flagKeys.forEach((key) => this._onChange(key)); | ||
| } |
There was a problem hiding this comment.
Override dependents miss LD change notifications
Medium Severity
OverrideSink fans change notifications through the merged view only when the override layer itself is replaced. LaunchDarkly upserts still go through TransactionalDataSourceUpdates on the raw feature store, whose dependency graph never includes override definitions. A flag or segment that exists only in the overlay, or whose override edges differ from LaunchDarkly data, is not notified when the LaunchDarkly prerequisite or segment it reads actually changes, so listeners can keep a stale evaluation.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4fc2262. Configure here.


Summary
This PR is based on the evaluator marking branch because it uses the marker and the marking that branch adds.
This change adds the override layer described by the OVERRIDE specification to the FDv2 data system of the Node.js server SDK.
An override source is configured with the new
overridesproperty of the data system options, which accepts a source object or a factory function. A source is not a data source: it takes no part in the initializer and synchronizer pipeline and has no effect on initialization status, data availability, or data source status. The SDK starts it when the client is created, before the data source, passes it a sink, and closes it with the client. In offline mode the source is not started. The publicLDOverrideSourceandLDOverrideSinkinterfaces let an application supply a custom source. A configuration that is not a source fails client construction, the way an unsupported data source configuration does.The source supplies complete snapshots to the override store. Each snapshot replaces the whole store in one assignment, so the store holds exactly one snapshot at any instant. The store holds prepared, marked copies of the definitions and never modifies the objects the source supplied. An overlay at the store read boundary returns the override entry for a key in preference to LaunchDarkly data, and an enumeration includes every flag the store holds. Evaluation, prerequisite and segment resolution, and the all-flags read all go through the overlay.
The client consults the override store before the not-initialized short circuit. An overridden flag is served before the client has LaunchDarkly data, while a flag the store does not hold still gets the not-ready default. Because the client is created synchronously and a file read is not, the evaluation methods and
allFlagsStatewait for the source's initial load to complete, so an override present at startup takes effect from the first evaluation. A failed start is logged and the client runs without overrides.The all-flags state keeps override-affected flags with their values, versions, and marked reasons, and presents them with
trackEventsandtrackReasonfalse and nodebugEventsUntilDate. While the client is not initialized and the store holds entries, the state is valid and holds only those flags. A type mismatch keeps the marking of the evaluation it replaces. Adding, changing, or removing an override fires the normal flag change notifications, including for the flags that depend on a changed flag or segment.The OVERRIDE specification's test vectors are vendored and run as a unit test through the full client stack, asserting the value, the variation index, and the reason.
SDK-3247
Note
Overview
Adds an experimental flag override layer to the FDv2 data system via
dataSystem.overrides, wired through newLDOverrideSource/LDOverrideSinkAPIs.An override source pushes full snapshots into an
OverrideLayer; reads go through aReadStoreOverlayso per-key overrides win over LaunchDarkly data without affecting client initialization or data-source status.LDClientImplstarts the source before the data source (skipped in offline mode), waits on async initial load beforevariation*/allFlagsState, and serves overridden flags even when the client is not initialized.Override-affected evaluations get
overrideAffectedon reasons;allFlagsStatestrips per-flag tracking fields for those flags while keeping values/reasons.OverrideSinkfans out flag-change notifications using dependency tracking (prerequisites and segments). OVERRIDE spec vectors are vendored and exercised through the full client stack.Also exports the override types from the public API and validates override configuration in
Configuration(invalid types warn and drop; malformed sources throw at construction).Reviewed by Cursor Bugbot for commit 4fc2262. Bugbot is set up for automated code reviews on this repo. Configure here.