@launchdarkly/js-sdk-common 2.24.3 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to `@launchdarkly/js-sdk-common` will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## [2.25.0](https://github.com/launchdarkly/js-core/compare/js-sdk-common-v2.24.4...js-sdk-common-v2.25.0) (2026-05-21)
6
+
7
+
8
+ ### Features
9
+
10
+ * add X-LaunchDarkly-Instance-Id header to server-node SDK (SDK-2358) ([#1377](https://github.com/launchdarkly/js-core/issues/1377)) ([814dc0b](https://github.com/launchdarkly/js-core/commit/814dc0bfd6b152385f3a758f9eeca37a0f9f08e8))
11
+
12
+ ## [2.24.4](https://github.com/launchdarkly/js-core/compare/js-sdk-common-v2.24.3...js-sdk-common-v2.24.4) (2026-05-06)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **server-node:** honor x-ld-fd-fallback directive in FDv2 initializer phase ([#1342](https://github.com/launchdarkly/js-core/issues/1342)) ([a80eaca](https://github.com/launchdarkly/js-core/commit/a80eacaafa6174e5f1b4fe21ba11534fdf1f92a8))
18
+
5
19
  ## [2.24.3](https://github.com/launchdarkly/js-core/compare/js-sdk-common-v2.24.2...js-sdk-common-v2.24.3) (2026-04-30)
6
20
 
7
21
 
@@ -1,19 +1,9 @@
1
- /**
2
- * @experimental
3
- * This feature is not stable and not subject to any backwards compatibility guarantees or semantic
4
- * versioning. It is not suitable for production usage.
5
- */
6
1
  export declare enum DataSourceState {
7
2
  Valid = 0,
8
3
  Initializing = 1,
9
4
  Interrupted = 2,
10
5
  Closed = 3
11
6
  }
12
- /**
13
- * @experimental
14
- * This feature is not stable and not subject to any backwards compatibility guarantees or semantic
15
- * versioning. It is not suitable for production usage.
16
- */
17
7
  export interface DataSource {
18
8
  /**
19
9
  * May be called any number of times, if already started, has no effect
@@ -28,10 +18,5 @@ export interface DataSource {
28
18
  */
29
19
  stop(): void;
30
20
  }
31
- /**
32
- * @experimental
33
- * This feature is not stable and not subject to any backwards compatibility guarantees or semantic
34
- * versioning. It is not suitable for production usage.
35
- */
36
21
  export type LDDataSourceFactory = () => DataSource;
37
22
  //# sourceMappingURL=DataSource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataSource.d.ts","sourceRoot":"","sources":["../../../../src/api/subsystem/DataSystem/DataSource.ts"],"names":[],"mappings":"AACA;;;;GAIG;AACH,oBAAY,eAAe;IAEzB,KAAK,IAAA;IAEL,YAAY,IAAA;IAEZ,WAAW,IAAA;IAEX,MAAM,IAAA;CACP;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,KAAK,CACH,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,EACjD,cAAc,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,EAC5D,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GACxC,IAAI,CAAC;IAER;;OAEG;IACH,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"DataSource.d.ts","sourceRoot":"","sources":["../../../../src/api/subsystem/DataSystem/DataSource.ts"],"names":[],"mappings":"AACA,oBAAY,eAAe;IAEzB,KAAK,IAAA;IAEL,YAAY,IAAA;IAEZ,WAAW,IAAA;IAEX,MAAM,IAAA;CACP;AAED,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,KAAK,CACH,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,EACjD,cAAc,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,EAC5D,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GACxC,IAAI,CAAC;IAER;;OAEG;IACH,IAAI,IAAI,IAAI,CAAC;CACd;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,UAAU,CAAC"}
@@ -26,6 +26,7 @@ export declare class CompositeDataSource implements DataSource {
26
26
  private _initFactories;
27
27
  private _syncFactories;
28
28
  private _fdv1Synchronizers;
29
+ private _fdv1FallbackEngaged;
29
30
  private _stopped;
30
31
  private _externalTransitionPromise;
31
32
  private _externalTransitionResolve?;
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeDataSource.d.ts","sourceRoot":"","sources":["../../src/datasource/CompositeDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EACL,UAAU,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AAOpD;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1E;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;KAChC,CAAC,IAAI,eAAe,CAAC,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,UAAU,CAAA;KAAE;CACxE,CAAC;AAOF;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IAyBlD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB;IAUtC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjC3B,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,kBAAkB,CAAsC;IAEhE,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,0BAA0B,CAA6B;IAC/D,OAAO,CAAC,0BAA0B,CAAC,CAAqC;IACxE,OAAO,CAAC,aAAa,CAAsB;IAE3C;;;;;;;OAOG;gBAED,YAAY,EAAE,mBAAmB,EAAE,EACnC,aAAa,EAAE,mBAAmB,EAAE,EACpC,iBAAiB,EAAE,mBAAmB,EAAE,EACvB,OAAO,CAAC,sBAAU,EAClB,qBAAqB,GAAE,oBASvC,EACgB,QAAQ,GAAE,OAAyC;IAWhE,KAAK,CACT,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,EACjD,cAAc,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,EAC5D,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GACxC,OAAO,CAAC,IAAI,CAAC;IAwJV,IAAI;IAMV,OAAO,CAAC,MAAM;IAYd;;;;OAIG;IACH,OAAO,CAAC,eAAe;IA0DvB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAclC,OAAO,CAAC,iBAAiB,CAcvB;IAEF,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;OAMG;IACH,OAAO,CAAC,gCAAgC;CAgCzC"}
1
+ {"version":3,"file":"CompositeDataSource.d.ts","sourceRoot":"","sources":["../../src/datasource/CompositeDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EACL,UAAU,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AAOpD;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1E;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;KAChC,CAAC,IAAI,eAAe,CAAC,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,UAAU,CAAA;KAAE;CACxE,CAAC;AAOF;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IA8BlD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB;IAUtC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAtC3B,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,kBAAkB,CAAsC;IAKhE,OAAO,CAAC,oBAAoB,CAAkB;IAE9C,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,0BAA0B,CAA6B;IAC/D,OAAO,CAAC,0BAA0B,CAAC,CAAqC;IACxE,OAAO,CAAC,aAAa,CAAsB;IAE3C;;;;;;;OAOG;gBAED,YAAY,EAAE,mBAAmB,EAAE,EACnC,aAAa,EAAE,mBAAmB,EAAE,EACpC,iBAAiB,EAAE,mBAAmB,EAAE,EACvB,OAAO,CAAC,sBAAU,EAClB,qBAAqB,GAAE,oBASvC,EACgB,QAAQ,GAAE,OAAyC;IAWhE,KAAK,CACT,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,EACjD,cAAc,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,EAC5D,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GACxC,OAAO,CAAC,IAAI,CAAC;IAyMV,IAAI;IAMV,OAAO,CAAC,MAAM;IAad;;;;OAIG;IACH,OAAO,CAAC,eAAe;IA0DvB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAclC,OAAO,CAAC,iBAAiB,CAcvB;IAEF,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;OAMG;IACH,OAAO,CAAC,gCAAgC;CAgCzC"}
@@ -985,11 +985,6 @@ class CallbackHandler {
985
985
  }
986
986
 
987
987
  // TODO: refactor client-sdk to use this enum
988
- /**
989
- * @experimental
990
- * This feature is not stable and not subject to any backwards compatibility guarantees or semantic
991
- * versioning. It is not suitable for production usage.
992
- */
993
988
  var DataSourceState;
994
989
  (function (DataSourceState) {
995
990
  // Positive confirmation of connection/data receipt
@@ -1155,6 +1150,11 @@ class CompositeDataSource {
1155
1150
  this._logger = _logger;
1156
1151
  this._transitionConditions = _transitionConditions;
1157
1152
  this._backoff = _backoff;
1153
+ // Set after the server-directed FDv1 fallback has been engaged. The directive is
1154
+ // one-way: subsequent fallback signals from the FDv1 synchronizer (e.g. if the FDv1
1155
+ // endpoint were to echo `x-ld-fd-fallback`) are ignored so the data source does not
1156
+ // loop replacing its synchronizer list with itself.
1157
+ this._fdv1FallbackEngaged = false;
1158
1158
  this._stopped = true;
1159
1159
  this._cancelTokens = [];
1160
1160
  this._cancellableDelay = (delayMS) => {
@@ -1202,10 +1202,36 @@ class CompositeDataSource {
1202
1202
  // secondary has been valid for N many seconds)
1203
1203
  let lastState;
1204
1204
  let cancelScheduledTransition = () => { };
1205
+ // Engages the FDv1 Fallback Directive: swaps the synchronizer list to FDv1 (or
1206
+ // empties it when no FDv1 fallback is configured), reports the in-progress status
1207
+ // through the sanitizer, cancels any pending scheduled transition, and resolves
1208
+ // a switchToSync transition. The directive is one-way; the engagement flag is
1209
+ // checked by the callers so we don't re-enter this branch on repeated signals.
1210
+ const engageFDv1Fallback = (statusToReport, err) => {
1211
+ if (this._fdv1Synchronizers.length() > 0) {
1212
+ this._logger?.warn(`Falling back to FDv1`);
1213
+ }
1214
+ else {
1215
+ this._logger?.warn(`FDv1 fallback was requested but no FDv1 fallback synchronizer is configured; data source will terminate`);
1216
+ }
1217
+ this._fdv1FallbackEngaged = true;
1218
+ this._syncFactories = this._fdv1Synchronizers;
1219
+ sanitizedStatusCallback(statusToReport, err);
1220
+ this._consumeCancelToken(cancelScheduledTransition);
1221
+ transitionResolve({ transition: 'switchToSync', err: err });
1222
+ };
1205
1223
  // this callback handler can be disabled and ensures only one transition request occurs
1206
1224
  const callbackHandler = new CallbackHandler((basis, data) => {
1207
1225
  this._backoff.success();
1208
1226
  dataCallback(basis, data);
1227
+ // The FDv1 fallback directive can ride along on the same data callback that
1228
+ // delivers a payload, so the swap to FDv1 is atomic with payload application.
1229
+ // Once engaged, the directive is one-way -- subsequent signals are ignored.
1230
+ if (data?.fallbackToFDv1 && !this._fdv1FallbackEngaged) {
1231
+ callbackHandler.disable();
1232
+ engageFDv1Fallback(DataSourceState.Interrupted, undefined);
1233
+ return;
1234
+ }
1209
1235
  if (basis && this._initPhaseActive) {
1210
1236
  // transition to sync if we get basis during init
1211
1237
  callbackHandler.disable();
@@ -1224,11 +1250,13 @@ class CompositeDataSource {
1224
1250
  // don't use this datasource's factory again
1225
1251
  this._logger?.debug(`Culling data source due to err ${err}`);
1226
1252
  cullDSFactory?.();
1227
- // this error indicates we should fallback to only using FDv1 synchronizers
1228
- if (err instanceof LDFlagDeliveryFallbackError) {
1229
- this._logger?.debug(`Falling back to FDv1`);
1230
- this._syncFactories = this._fdv1Synchronizers;
1231
- }
1253
+ }
1254
+ // Status-callback path for the FDv1 Fallback Directive (used when the
1255
+ // server-directed signal arrives without an accompanying payload, e.g. on
1256
+ // an HTTP error or a malformed body).
1257
+ if (err instanceof LDFlagDeliveryFallbackError && !this._fdv1FallbackEngaged) {
1258
+ engageFDv1Fallback(state, err);
1259
+ return;
1232
1260
  }
1233
1261
  sanitizedStatusCallback(state, err);
1234
1262
  this._consumeCancelToken(cancelScheduledTransition);
@@ -1275,7 +1303,16 @@ class CompositeDataSource {
1275
1303
  ]);
1276
1304
  // stop the underlying datasource before transitioning to next state
1277
1305
  currentDS?.stop();
1278
- if (transitionRequest.err && transitionRequest.transition !== 'stop') {
1306
+ // Skip backoff only on the initial server-directed FDv1 engagement (a `switchToSync`
1307
+ // transition resolved by `engageFDv1Fallback` carries an `LDFlagDeliveryFallbackError`).
1308
+ // Any subsequent `LDFlagDeliveryFallbackError` is an ordinary retry from the FDv1
1309
+ // synchronizer (or a follow-up FDv1 factory) and must be throttled like any other
1310
+ // error retry.
1311
+ const isInitialFDv1Engagement = transitionRequest.transition === 'switchToSync' &&
1312
+ transitionRequest.err instanceof LDFlagDeliveryFallbackError;
1313
+ if (transitionRequest.err &&
1314
+ transitionRequest.transition !== 'stop' &&
1315
+ !isInitialFDv1Engagement) {
1279
1316
  // if the transition was due to an error we're not in the initializer phase, throttle the transition. Fallback between initializers is not throttled.
1280
1317
  const delay = this._initPhaseActive ? 0 : this._backoff.fail();
1281
1318
  const { promise, cancel: cancelDelay } = this._cancellableDelay(delay);
@@ -1313,6 +1350,7 @@ class CompositeDataSource {
1313
1350
  this._initFactories.reset();
1314
1351
  this._syncFactories.reset();
1315
1352
  this._fdv1Synchronizers.reset();
1353
+ this._fdv1FallbackEngaged = false;
1316
1354
  this._externalTransitionPromise = new Promise((tr) => {
1317
1355
  this._externalTransitionResolve = tr;
1318
1356
  });
@@ -2197,7 +2235,7 @@ function fastDeepEqual(a, b) {
2197
2235
  return a !== a && b !== b;
2198
2236
  }
2199
2237
 
2200
- function defaultHeaders(sdkKey, info, tags, includeAuthorizationHeader = true, userAgentHeaderName = 'user-agent') {
2238
+ function defaultHeaders(sdkKey, info, tags, includeAuthorizationHeader = true, userAgentHeaderName = 'user-agent', instanceId) {
2201
2239
  const { userAgentBase, version, wrapperName, wrapperVersion } = info.sdkData();
2202
2240
  const headers = {
2203
2241
  [userAgentHeaderName]: `${userAgentBase ?? 'NodeJSClient'}/${version}`,
@@ -2215,6 +2253,14 @@ function defaultHeaders(sdkKey, info, tags, includeAuthorizationHeader = true, u
2215
2253
  if (tags?.value) {
2216
2254
  headers['x-launchdarkly-tags'] = tags.value;
2217
2255
  }
2256
+ // Per SCMP-server-connection-minutes-polling (spec section 1.1), server SDKs include a
2257
+ // per-instance v4 GUID on every polling request. The caller (a server SDK) is responsible
2258
+ // for generating the GUID once per SDK instance and passing it here, so that it rides on
2259
+ // every outbound request via this shared default-headers map. Client/edge SDKs do not pass
2260
+ // this value, so the header is omitted for them.
2261
+ if (instanceId) {
2262
+ headers['x-launchdarkly-instance-id'] = instanceId;
2263
+ }
2218
2264
  return headers;
2219
2265
  }
2220
2266
  function httpErrorMessage(err, context, retryMessage) {