@launchdarkly/js-client-sdk-common 1.20.0 → 1.22.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.
Files changed (162) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/cjs/LDClientImpl.d.ts.map +1 -1
  3. package/dist/cjs/api/LDOptions.d.ts +8 -0
  4. package/dist/cjs/api/LDOptions.d.ts.map +1 -1
  5. package/dist/cjs/api/datasource/DataSourceEntry.d.ts +45 -0
  6. package/dist/cjs/api/datasource/DataSourceEntry.d.ts.map +1 -0
  7. package/dist/cjs/api/datasource/FDv2ConnectionMode.d.ts +30 -0
  8. package/dist/cjs/api/datasource/FDv2ConnectionMode.d.ts.map +1 -0
  9. package/dist/cjs/api/datasource/LDClientDataSystemOptions.d.ts +75 -0
  10. package/dist/cjs/api/datasource/LDClientDataSystemOptions.d.ts.map +1 -0
  11. package/dist/cjs/api/datasource/ModeDefinition.d.ts +21 -0
  12. package/dist/cjs/api/datasource/ModeDefinition.d.ts.map +1 -0
  13. package/dist/cjs/api/datasource/ModeResolution.d.ts +75 -0
  14. package/dist/cjs/api/datasource/ModeResolution.d.ts.map +1 -0
  15. package/dist/cjs/api/datasource/index.d.ts +6 -0
  16. package/dist/cjs/api/datasource/index.d.ts.map +1 -0
  17. package/dist/cjs/api/index.d.ts +1 -0
  18. package/dist/cjs/api/index.d.ts.map +1 -1
  19. package/dist/cjs/configuration/Configuration.d.ts +6 -1
  20. package/dist/cjs/configuration/Configuration.d.ts.map +1 -1
  21. package/dist/cjs/configuration/validateOptions.d.ts +95 -0
  22. package/dist/cjs/configuration/validateOptions.d.ts.map +1 -0
  23. package/dist/cjs/configuration/validators.d.ts +5 -2
  24. package/dist/cjs/configuration/validators.d.ts.map +1 -1
  25. package/dist/cjs/datasource/ConnectionModeConfig.d.ts +19 -0
  26. package/dist/cjs/datasource/ConnectionModeConfig.d.ts.map +1 -0
  27. package/dist/cjs/datasource/LDClientDataSystemOptions.d.ts +20 -0
  28. package/dist/cjs/datasource/LDClientDataSystemOptions.d.ts.map +1 -0
  29. package/dist/cjs/datasource/ModeResolver.d.ts +30 -0
  30. package/dist/cjs/datasource/ModeResolver.d.ts.map +1 -0
  31. package/dist/cjs/datasource/StateDebounceManager.d.ts +92 -0
  32. package/dist/cjs/datasource/StateDebounceManager.d.ts.map +1 -0
  33. package/dist/cjs/datasource/fdv2/AsyncQueue.d.ts +2 -0
  34. package/dist/cjs/datasource/fdv2/AsyncQueue.d.ts.map +1 -0
  35. package/dist/cjs/datasource/fdv2/CacheInitializer.d.ts +17 -0
  36. package/dist/cjs/datasource/fdv2/CacheInitializer.d.ts.map +1 -0
  37. package/dist/cjs/datasource/fdv2/Conditions.d.ts +73 -0
  38. package/dist/cjs/datasource/fdv2/Conditions.d.ts.map +1 -0
  39. package/dist/cjs/datasource/fdv2/FDv1PollingSynchronizer.d.ts +2 -0
  40. package/dist/cjs/datasource/fdv2/FDv1PollingSynchronizer.d.ts.map +1 -0
  41. package/dist/cjs/datasource/fdv2/FDv2DataSource.d.ts +52 -0
  42. package/dist/cjs/datasource/fdv2/FDv2DataSource.d.ts.map +1 -0
  43. package/dist/cjs/datasource/fdv2/FDv2Requestor.d.ts +45 -0
  44. package/dist/cjs/datasource/fdv2/FDv2Requestor.d.ts.map +1 -0
  45. package/dist/cjs/datasource/fdv2/FDv2SourceResult.d.ts +84 -0
  46. package/dist/cjs/datasource/fdv2/FDv2SourceResult.d.ts.map +1 -0
  47. package/dist/cjs/datasource/fdv2/Initializer.d.ts +40 -0
  48. package/dist/cjs/datasource/fdv2/Initializer.d.ts.map +1 -0
  49. package/dist/cjs/datasource/fdv2/PollingBase.d.ts +2 -0
  50. package/dist/cjs/datasource/fdv2/PollingBase.d.ts.map +1 -0
  51. package/dist/cjs/datasource/fdv2/PollingInitializer.d.ts +2 -0
  52. package/dist/cjs/datasource/fdv2/PollingInitializer.d.ts.map +1 -0
  53. package/dist/cjs/datasource/fdv2/PollingSynchronizer.d.ts +2 -0
  54. package/dist/cjs/datasource/fdv2/PollingSynchronizer.d.ts.map +1 -0
  55. package/dist/cjs/datasource/fdv2/SourceManager.d.ts +88 -0
  56. package/dist/cjs/datasource/fdv2/SourceManager.d.ts.map +1 -0
  57. package/dist/cjs/datasource/fdv2/StreamingFDv2Base.d.ts +11 -0
  58. package/dist/cjs/datasource/fdv2/StreamingFDv2Base.d.ts.map +1 -0
  59. package/dist/cjs/datasource/fdv2/StreamingInitializerFDv2.d.ts +2 -0
  60. package/dist/cjs/datasource/fdv2/StreamingInitializerFDv2.d.ts.map +1 -0
  61. package/dist/cjs/datasource/fdv2/StreamingSynchronizerFDv2.d.ts +2 -0
  62. package/dist/cjs/datasource/fdv2/StreamingSynchronizerFDv2.d.ts.map +1 -0
  63. package/dist/cjs/datasource/fdv2/Synchronizer.d.ts +49 -0
  64. package/dist/cjs/datasource/fdv2/Synchronizer.d.ts.map +1 -0
  65. package/dist/cjs/datasource/fdv2/calculatePollDelay.d.ts +2 -0
  66. package/dist/cjs/datasource/fdv2/calculatePollDelay.d.ts.map +1 -0
  67. package/dist/cjs/datasource/fdv2/index.d.ts +20 -0
  68. package/dist/cjs/datasource/fdv2/index.d.ts.map +1 -0
  69. package/dist/cjs/datasource/flagEvalMapper.d.ts +3 -3
  70. package/dist/cjs/flag-manager/FlagManager.d.ts +2 -1
  71. package/dist/cjs/flag-manager/FlagManager.d.ts.map +1 -1
  72. package/dist/cjs/flag-manager/FlagPersistence.d.ts +8 -1
  73. package/dist/cjs/flag-manager/FlagPersistence.d.ts.map +1 -1
  74. package/dist/cjs/index.cjs +411 -100
  75. package/dist/cjs/index.cjs.map +1 -1
  76. package/dist/cjs/index.d.ts +5 -0
  77. package/dist/cjs/index.d.ts.map +1 -1
  78. package/dist/cjs/storage/freshness.d.ts +27 -0
  79. package/dist/cjs/storage/freshness.d.ts.map +1 -0
  80. package/dist/cjs/storage/loadCachedFlags.d.ts +25 -0
  81. package/dist/cjs/storage/loadCachedFlags.d.ts.map +1 -0
  82. package/dist/esm/LDClientImpl.d.ts.map +1 -1
  83. package/dist/esm/api/LDOptions.d.ts +8 -0
  84. package/dist/esm/api/LDOptions.d.ts.map +1 -1
  85. package/dist/esm/api/datasource/DataSourceEntry.d.ts +45 -0
  86. package/dist/esm/api/datasource/DataSourceEntry.d.ts.map +1 -0
  87. package/dist/esm/api/datasource/FDv2ConnectionMode.d.ts +30 -0
  88. package/dist/esm/api/datasource/FDv2ConnectionMode.d.ts.map +1 -0
  89. package/dist/esm/api/datasource/LDClientDataSystemOptions.d.ts +75 -0
  90. package/dist/esm/api/datasource/LDClientDataSystemOptions.d.ts.map +1 -0
  91. package/dist/esm/api/datasource/ModeDefinition.d.ts +21 -0
  92. package/dist/esm/api/datasource/ModeDefinition.d.ts.map +1 -0
  93. package/dist/esm/api/datasource/ModeResolution.d.ts +75 -0
  94. package/dist/esm/api/datasource/ModeResolution.d.ts.map +1 -0
  95. package/dist/esm/api/datasource/index.d.ts +6 -0
  96. package/dist/esm/api/datasource/index.d.ts.map +1 -0
  97. package/dist/esm/api/index.d.ts +1 -0
  98. package/dist/esm/api/index.d.ts.map +1 -1
  99. package/dist/esm/configuration/Configuration.d.ts +6 -1
  100. package/dist/esm/configuration/Configuration.d.ts.map +1 -1
  101. package/dist/esm/configuration/validateOptions.d.ts +95 -0
  102. package/dist/esm/configuration/validateOptions.d.ts.map +1 -0
  103. package/dist/esm/configuration/validators.d.ts +5 -2
  104. package/dist/esm/configuration/validators.d.ts.map +1 -1
  105. package/dist/esm/datasource/ConnectionModeConfig.d.ts +19 -0
  106. package/dist/esm/datasource/ConnectionModeConfig.d.ts.map +1 -0
  107. package/dist/esm/datasource/LDClientDataSystemOptions.d.ts +20 -0
  108. package/dist/esm/datasource/LDClientDataSystemOptions.d.ts.map +1 -0
  109. package/dist/esm/datasource/ModeResolver.d.ts +30 -0
  110. package/dist/esm/datasource/ModeResolver.d.ts.map +1 -0
  111. package/dist/esm/datasource/StateDebounceManager.d.ts +92 -0
  112. package/dist/esm/datasource/StateDebounceManager.d.ts.map +1 -0
  113. package/dist/esm/datasource/fdv2/AsyncQueue.d.ts +2 -0
  114. package/dist/esm/datasource/fdv2/AsyncQueue.d.ts.map +1 -0
  115. package/dist/esm/datasource/fdv2/CacheInitializer.d.ts +17 -0
  116. package/dist/esm/datasource/fdv2/CacheInitializer.d.ts.map +1 -0
  117. package/dist/esm/datasource/fdv2/Conditions.d.ts +73 -0
  118. package/dist/esm/datasource/fdv2/Conditions.d.ts.map +1 -0
  119. package/dist/esm/datasource/fdv2/FDv1PollingSynchronizer.d.ts +2 -0
  120. package/dist/esm/datasource/fdv2/FDv1PollingSynchronizer.d.ts.map +1 -0
  121. package/dist/esm/datasource/fdv2/FDv2DataSource.d.ts +52 -0
  122. package/dist/esm/datasource/fdv2/FDv2DataSource.d.ts.map +1 -0
  123. package/dist/esm/datasource/fdv2/FDv2Requestor.d.ts +45 -0
  124. package/dist/esm/datasource/fdv2/FDv2Requestor.d.ts.map +1 -0
  125. package/dist/esm/datasource/fdv2/FDv2SourceResult.d.ts +84 -0
  126. package/dist/esm/datasource/fdv2/FDv2SourceResult.d.ts.map +1 -0
  127. package/dist/esm/datasource/fdv2/Initializer.d.ts +40 -0
  128. package/dist/esm/datasource/fdv2/Initializer.d.ts.map +1 -0
  129. package/dist/esm/datasource/fdv2/PollingBase.d.ts +2 -0
  130. package/dist/esm/datasource/fdv2/PollingBase.d.ts.map +1 -0
  131. package/dist/esm/datasource/fdv2/PollingInitializer.d.ts +2 -0
  132. package/dist/esm/datasource/fdv2/PollingInitializer.d.ts.map +1 -0
  133. package/dist/esm/datasource/fdv2/PollingSynchronizer.d.ts +2 -0
  134. package/dist/esm/datasource/fdv2/PollingSynchronizer.d.ts.map +1 -0
  135. package/dist/esm/datasource/fdv2/SourceManager.d.ts +88 -0
  136. package/dist/esm/datasource/fdv2/SourceManager.d.ts.map +1 -0
  137. package/dist/esm/datasource/fdv2/StreamingFDv2Base.d.ts +11 -0
  138. package/dist/esm/datasource/fdv2/StreamingFDv2Base.d.ts.map +1 -0
  139. package/dist/esm/datasource/fdv2/StreamingInitializerFDv2.d.ts +2 -0
  140. package/dist/esm/datasource/fdv2/StreamingInitializerFDv2.d.ts.map +1 -0
  141. package/dist/esm/datasource/fdv2/StreamingSynchronizerFDv2.d.ts +2 -0
  142. package/dist/esm/datasource/fdv2/StreamingSynchronizerFDv2.d.ts.map +1 -0
  143. package/dist/esm/datasource/fdv2/Synchronizer.d.ts +49 -0
  144. package/dist/esm/datasource/fdv2/Synchronizer.d.ts.map +1 -0
  145. package/dist/esm/datasource/fdv2/calculatePollDelay.d.ts +2 -0
  146. package/dist/esm/datasource/fdv2/calculatePollDelay.d.ts.map +1 -0
  147. package/dist/esm/datasource/fdv2/index.d.ts +20 -0
  148. package/dist/esm/datasource/fdv2/index.d.ts.map +1 -0
  149. package/dist/esm/datasource/flagEvalMapper.d.ts +3 -3
  150. package/dist/esm/flag-manager/FlagManager.d.ts +2 -1
  151. package/dist/esm/flag-manager/FlagManager.d.ts.map +1 -1
  152. package/dist/esm/flag-manager/FlagPersistence.d.ts +8 -1
  153. package/dist/esm/flag-manager/FlagPersistence.d.ts.map +1 -1
  154. package/dist/esm/index.d.ts +5 -0
  155. package/dist/esm/index.d.ts.map +1 -1
  156. package/dist/esm/index.mjs +404 -102
  157. package/dist/esm/index.mjs.map +1 -1
  158. package/dist/esm/storage/freshness.d.ts +27 -0
  159. package/dist/esm/storage/freshness.d.ts.map +1 -0
  160. package/dist/esm/storage/loadCachedFlags.d.ts +25 -0
  161. package/dist/esm/storage/loadCachedFlags.d.ts.map +1 -0
  162. package/package.json +2 -2
@@ -222,34 +222,208 @@ function createAsyncTaskQueue(logger) {
222
222
  };
223
223
  }
224
224
 
225
- // eslint-disable-next-line max-classes-per-file
226
- const validators = {
227
- logger: jsSdkCommon.TypeValidators.Object,
228
- maxCachedContexts: jsSdkCommon.TypeValidators.numberWithMin(0),
229
- baseUri: jsSdkCommon.TypeValidators.String,
230
- streamUri: jsSdkCommon.TypeValidators.String,
231
- eventsUri: jsSdkCommon.TypeValidators.String,
232
- capacity: jsSdkCommon.TypeValidators.numberWithMin(1),
233
- diagnosticRecordingInterval: jsSdkCommon.TypeValidators.numberWithMin(2),
234
- flushInterval: jsSdkCommon.TypeValidators.numberWithMin(2),
235
- streamInitialReconnectDelay: jsSdkCommon.TypeValidators.numberWithMin(0),
236
- allAttributesPrivate: jsSdkCommon.TypeValidators.Boolean,
237
- debug: jsSdkCommon.TypeValidators.Boolean,
238
- diagnosticOptOut: jsSdkCommon.TypeValidators.Boolean,
239
- withReasons: jsSdkCommon.TypeValidators.Boolean,
240
- sendEvents: jsSdkCommon.TypeValidators.Boolean,
225
+ function isCompoundValidator(v) {
226
+ return 'validate' in v;
227
+ }
228
+ /**
229
+ * Validates an options object against a map of validators and defaults.
230
+ *
231
+ * If `input` is null, undefined, or not an object the defaults are returned
232
+ * (with a warning for non-nullish non-objects).
233
+ *
234
+ * Supports special validator types created by:
235
+ * - {@link validatorOf}: recursively validates nested objects
236
+ * - {@link arrayOf}: validates arrays with per-item validation
237
+ * - {@link anyOf}: accepts the first matching validator from a list
238
+ * - {@link recordOf}: validates objects with dynamic keys
239
+ */
240
+ function validateOptions(input, validatorMap, defaults, logger, prefix) {
241
+ const result = { ...defaults };
242
+ if (jsSdkCommon.isNullish(input)) {
243
+ return result;
244
+ }
245
+ if (!jsSdkCommon.TypeValidators.Object.is(input)) {
246
+ logger?.warn(jsSdkCommon.OptionMessages.wrongOptionType(prefix ?? 'config', 'object', typeof input));
247
+ return result;
248
+ }
249
+ Object.entries(input).forEach(([key, value]) => {
250
+ const validator = validatorMap[key];
251
+ const name = prefix ? `${prefix}.${key}` : key;
252
+ if (!validator) {
253
+ logger?.warn(jsSdkCommon.OptionMessages.unknownOption(name));
254
+ return;
255
+ }
256
+ if (jsSdkCommon.isNullish(value)) {
257
+ return;
258
+ }
259
+ if (isCompoundValidator(validator)) {
260
+ const validated = validator.validate(value, name, logger, defaults[key]);
261
+ if (validated !== undefined) {
262
+ result[key] = validated.value;
263
+ }
264
+ return;
265
+ }
266
+ if (validator.is(value)) {
267
+ result[key] = value;
268
+ return;
269
+ }
270
+ // Validation failed — apply correction or fall back to default.
271
+ const validatorType = validator.getType();
272
+ if (validatorType === 'boolean') {
273
+ logger?.warn(jsSdkCommon.OptionMessages.wrongOptionTypeBoolean(name, typeof value));
274
+ result[key] = !!value;
275
+ }
276
+ else if (validatorType === 'boolean | undefined | null') {
277
+ logger?.warn(jsSdkCommon.OptionMessages.wrongOptionTypeBoolean(name, typeof value));
278
+ if (typeof value !== 'boolean' && typeof value !== 'undefined' && value !== null) {
279
+ result[key] = !!value;
280
+ }
281
+ }
282
+ else if (validator instanceof jsSdkCommon.NumberWithMinimum && jsSdkCommon.TypeValidators.Number.is(value)) {
283
+ logger?.warn(jsSdkCommon.OptionMessages.optionBelowMinimum(name, value, validator.min));
284
+ result[key] = validator.min;
285
+ }
286
+ else {
287
+ logger?.warn(jsSdkCommon.OptionMessages.wrongOptionType(name, validatorType, typeof value));
288
+ }
289
+ });
290
+ return result;
291
+ }
292
+ /**
293
+ * Creates a validator for nested objects. When used in a validator map,
294
+ * `validateOptions` will recursively validate the nested object's properties.
295
+ * Defaults for nested fields are passed through from the parent.
296
+ */
297
+ function validatorOf(validators, builtInDefaults) {
298
+ return {
299
+ is: (u) => jsSdkCommon.TypeValidators.Object.is(u),
300
+ getType: () => 'object',
301
+ validate(value, name, logger, defaults) {
302
+ if (!jsSdkCommon.TypeValidators.Object.is(value)) {
303
+ logger?.warn(jsSdkCommon.OptionMessages.wrongOptionType(name, 'object', typeof value));
304
+ return undefined;
305
+ }
306
+ const nestedDefaults = builtInDefaults ??
307
+ (jsSdkCommon.TypeValidators.Object.is(defaults) ? defaults : {});
308
+ const nested = validateOptions(value, validators, nestedDefaults, logger, name);
309
+ return Object.keys(nested).length > 0 ? { value: nested } : undefined;
310
+ },
311
+ };
312
+ }
313
+ /**
314
+ * Creates a validator that tries each provided validator in order and uses the
315
+ * first one whose `is()` check passes. For compound validators the value is
316
+ * processed through `validate()`; for simple validators the value is accepted
317
+ * as-is. If no validator matches, a warning is logged and the default is
318
+ * preserved.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * // Accepts either a boolean or a nested object with specific fields:
323
+ * anyOf(TypeValidators.Boolean, validatorOf({ lifecycle: TypeValidators.Boolean }))
324
+ * ```
325
+ */
326
+ function anyOf(...validators) {
327
+ return {
328
+ is: (u) => validators.some((v) => v.is(u)),
329
+ getType: () => validators.map((v) => v.getType()).join(' | '),
330
+ validate(value, name, logger, defaults) {
331
+ const match = validators.find((v) => v.is(value));
332
+ if (match) {
333
+ return isCompoundValidator(match)
334
+ ? match.validate(value, name, logger, defaults)
335
+ : { value };
336
+ }
337
+ logger?.warn(jsSdkCommon.OptionMessages.wrongOptionType(name, this.getType(), typeof value));
338
+ return undefined;
339
+ },
340
+ };
341
+ }
342
+
343
+ const dataSourceTypeValidator = jsSdkCommon.TypeValidators.oneOf('cache', 'polling', 'streaming');
344
+ const connectionModeValidator = jsSdkCommon.TypeValidators.oneOf('streaming', 'polling', 'offline', 'one-shot', 'background');
345
+ const endpointValidators = {
346
+ pollingBaseUri: jsSdkCommon.TypeValidators.String,
347
+ streamingBaseUri: jsSdkCommon.TypeValidators.String,
348
+ };
349
+ ({
350
+ type: dataSourceTypeValidator,
241
351
  pollInterval: jsSdkCommon.TypeValidators.numberWithMin(30),
242
- useReport: jsSdkCommon.TypeValidators.Boolean,
243
- privateAttributes: jsSdkCommon.TypeValidators.StringArray,
244
- applicationInfo: jsSdkCommon.TypeValidators.Object,
245
- wrapperName: jsSdkCommon.TypeValidators.String,
246
- wrapperVersion: jsSdkCommon.TypeValidators.String,
247
- payloadFilterKey: jsSdkCommon.TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
248
- hooks: jsSdkCommon.TypeValidators.createTypeArray('Hook[]', {}),
249
- inspectors: jsSdkCommon.TypeValidators.createTypeArray('LDInspection', {}),
250
- cleanOldPersistentData: jsSdkCommon.TypeValidators.Boolean,
352
+ endpoints: validatorOf(endpointValidators),
353
+ });
354
+ ({
355
+ type: dataSourceTypeValidator,
356
+ initialReconnectDelay: jsSdkCommon.TypeValidators.numberWithMin(1),
357
+ endpoints: validatorOf(endpointValidators),
358
+ });
359
+
360
+ const modeSwitchingValidators = {
361
+ lifecycle: jsSdkCommon.TypeValidators.Boolean,
362
+ network: jsSdkCommon.TypeValidators.Boolean,
363
+ };
364
+ const dataSystemValidators = {
365
+ initialConnectionMode: connectionModeValidator,
366
+ backgroundConnectionMode: connectionModeValidator,
367
+ automaticModeSwitching: anyOf(jsSdkCommon.TypeValidators.Boolean, validatorOf(modeSwitchingValidators)),
368
+ };
369
+ /**
370
+ * Default FDv2 data system configuration for browser SDKs.
371
+ */
372
+ const BROWSER_DATA_SYSTEM_DEFAULTS = {
373
+ initialConnectionMode: 'one-shot',
374
+ backgroundConnectionMode: undefined,
375
+ automaticModeSwitching: false,
376
+ };
377
+ /**
378
+ * Default FDv2 data system configuration for mobile (React Native) SDKs.
379
+ */
380
+ const MOBILE_DATA_SYSTEM_DEFAULTS = {
381
+ initialConnectionMode: 'streaming',
382
+ backgroundConnectionMode: 'background',
383
+ automaticModeSwitching: true,
384
+ };
385
+ /**
386
+ * Default FDv2 data system configuration for desktop SDKs (Electron, etc.).
387
+ */
388
+ const DESKTOP_DATA_SYSTEM_DEFAULTS = {
389
+ initialConnectionMode: 'streaming',
390
+ backgroundConnectionMode: undefined,
391
+ automaticModeSwitching: false,
251
392
  };
252
393
 
394
+ function createValidators(options) {
395
+ return {
396
+ logger: jsSdkCommon.TypeValidators.Object,
397
+ maxCachedContexts: jsSdkCommon.TypeValidators.numberWithMin(0),
398
+ baseUri: jsSdkCommon.TypeValidators.String,
399
+ streamUri: jsSdkCommon.TypeValidators.String,
400
+ eventsUri: jsSdkCommon.TypeValidators.String,
401
+ capacity: jsSdkCommon.TypeValidators.numberWithMin(1),
402
+ diagnosticRecordingInterval: jsSdkCommon.TypeValidators.numberWithMin(2),
403
+ flushInterval: jsSdkCommon.TypeValidators.numberWithMin(2),
404
+ streamInitialReconnectDelay: jsSdkCommon.TypeValidators.numberWithMin(0),
405
+ allAttributesPrivate: jsSdkCommon.TypeValidators.Boolean,
406
+ debug: jsSdkCommon.TypeValidators.Boolean,
407
+ diagnosticOptOut: jsSdkCommon.TypeValidators.Boolean,
408
+ withReasons: jsSdkCommon.TypeValidators.Boolean,
409
+ sendEvents: jsSdkCommon.TypeValidators.Boolean,
410
+ pollInterval: jsSdkCommon.TypeValidators.numberWithMin(30),
411
+ useReport: jsSdkCommon.TypeValidators.Boolean,
412
+ privateAttributes: jsSdkCommon.TypeValidators.StringArray,
413
+ disableCache: jsSdkCommon.TypeValidators.Boolean,
414
+ applicationInfo: jsSdkCommon.TypeValidators.Object,
415
+ wrapperName: jsSdkCommon.TypeValidators.String,
416
+ wrapperVersion: jsSdkCommon.TypeValidators.String,
417
+ payloadFilterKey: jsSdkCommon.TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
418
+ hooks: jsSdkCommon.TypeValidators.createTypeArray('Hook[]', {}),
419
+ inspectors: jsSdkCommon.TypeValidators.createTypeArray('LDInspection', {}),
420
+ cleanOldPersistentData: jsSdkCommon.TypeValidators.Boolean,
421
+ dataSystem: options?.dataSystemDefaults
422
+ ? validatorOf(dataSystemValidators, options.dataSystemDefaults)
423
+ : jsSdkCommon.TypeValidators.Object,
424
+ };
425
+ }
426
+
253
427
  const DEFAULT_POLLING_INTERVAL = 60 * 5;
254
428
  const DEFAULT_POLLING = 'https://clientsdk.launchdarkly.com';
255
429
  const DEFAULT_STREAM = 'https://clientstream.launchdarkly.com';
@@ -275,6 +449,7 @@ class ConfigurationImpl {
275
449
  // eslint-disable-next-line @typescript-eslint/naming-convention
276
450
  this.streamUri = DEFAULT_STREAM;
277
451
  this.maxCachedContexts = 5;
452
+ this.disableCache = false;
278
453
  this.capacity = 100;
279
454
  this.diagnosticRecordingInterval = 900;
280
455
  this.flushInterval = 30;
@@ -291,8 +466,15 @@ class ConfigurationImpl {
291
466
  this.hooks = [];
292
467
  this.inspectors = [];
293
468
  this.logger = ensureSafeLogger(pristineOptions.logger);
294
- const errors = this._validateTypesAndNames(pristineOptions);
295
- errors.forEach((e) => this.logger.warn(e));
469
+ const validators = createValidators({
470
+ dataSystemDefaults: internalOptions.dataSystemDefaults,
471
+ });
472
+ const validated = validateOptions(pristineOptions, validators, {}, this.logger);
473
+ Object.entries(validated).forEach(([k, v]) => {
474
+ if (k !== 'logger') {
475
+ this[k] = v;
476
+ }
477
+ });
296
478
  this.serviceEndpoints = new jsSdkCommon.ServiceEndpoints(this.streamUri, this.baseUri, this.eventsUri, internalOptions.analyticsEventPath, internalOptions.diagnosticEventPath, internalOptions.includeAuthorizationHeader, pristineOptions.payloadFilterKey);
297
479
  this.useReport = pristineOptions.useReport ?? false;
298
480
  this.tags = new jsSdkCommon.ApplicationTags({ application: this.applicationInfo, logger: this.logger });
@@ -301,44 +483,6 @@ class ConfigurationImpl {
301
483
  this.credentialType = internalOptions.credentialType;
302
484
  this.getImplementationHooks = internalOptions.getImplementationHooks;
303
485
  }
304
- _validateTypesAndNames(pristineOptions) {
305
- const errors = [];
306
- Object.entries(pristineOptions).forEach(([k, v]) => {
307
- const validator = validators[k];
308
- if (validator) {
309
- if (!validator.is(v)) {
310
- const validatorType = validator.getType();
311
- if (validatorType === 'boolean') {
312
- errors.push(jsSdkCommon.OptionMessages.wrongOptionTypeBoolean(k, typeof v));
313
- this[k] = !!v;
314
- }
315
- else if (validatorType === 'boolean | undefined | null') {
316
- errors.push(jsSdkCommon.OptionMessages.wrongOptionTypeBoolean(k, typeof v));
317
- if (typeof v !== 'boolean' && typeof v !== 'undefined' && v !== null) {
318
- this[k] = !!v;
319
- }
320
- }
321
- else if (validator instanceof jsSdkCommon.NumberWithMinimum && jsSdkCommon.TypeValidators.Number.is(v)) {
322
- const { min } = validator;
323
- errors.push(jsSdkCommon.OptionMessages.optionBelowMinimum(k, v, min));
324
- this[k] = min;
325
- }
326
- else {
327
- errors.push(jsSdkCommon.OptionMessages.wrongOptionType(k, validator.getType(), typeof v));
328
- }
329
- }
330
- else if (k === 'logger') ;
331
- else {
332
- // if an option is explicitly null, coerce to undefined
333
- this[k] = v ?? undefined;
334
- }
335
- }
336
- else {
337
- errors.push(jsSdkCommon.OptionMessages.unknownOption(k));
338
- }
339
- });
340
- return errors;
341
- }
342
486
  }
343
487
 
344
488
  async function digest(hasher, encoding) {
@@ -677,6 +821,71 @@ class EventFactory extends jsSdkCommon.internal.EventFactoryBase {
677
821
  }
678
822
  }
679
823
 
824
+ /**
825
+ * Suffix appended to context storage keys to form the freshness storage key.
826
+ */
827
+ const FRESHNESS_SUFFIX = '_freshness';
828
+ /**
829
+ * Computes a SHA-256 hash of the context's full canonical JSON.
830
+ * Returns `undefined` if the context cannot be serialized.
831
+ */
832
+ async function hashContext(crypto, context) {
833
+ const json = context.canonicalUnfilteredJson();
834
+ if (!json) {
835
+ return undefined;
836
+ }
837
+ return digest(crypto.createHash('sha256').update(json), 'base64');
838
+ }
839
+
840
+ function isValidFlag(value) {
841
+ return value !== null && typeof value === 'object' && typeof value.version === 'number';
842
+ }
843
+ /**
844
+ * Loads cached flag data from storage for the given context.
845
+ *
846
+ * Checks the current storage key first, then falls back to the legacy
847
+ * canonical key location (pre-10.3.1). Does NOT perform migration — the
848
+ * caller is responsible for migrating data if {@link CachedFlagData.fromLegacyKey}
849
+ * is true.
850
+ *
851
+ * @returns The cached flag data, or `undefined` on cache miss or parse error.
852
+ */
853
+ async function loadCachedFlags(storage, crypto, environmentNamespace, context, logger) {
854
+ const storageKey = await namespaceForContextData(crypto, environmentNamespace, context);
855
+ let flagsJson = await storage.get(storageKey);
856
+ let fromLegacyKey = false;
857
+ if (flagsJson === null || flagsJson === undefined) {
858
+ // Fallback: in version <10.3.1 flag data was stored under the canonical key.
859
+ flagsJson = await storage.get(context.canonicalKey);
860
+ if (flagsJson === null || flagsJson === undefined) {
861
+ return undefined;
862
+ }
863
+ fromLegacyKey = true;
864
+ }
865
+ try {
866
+ const parsed = JSON.parse(flagsJson);
867
+ if (parsed === null || typeof parsed !== 'object') {
868
+ logger?.warn('Cached flag data is not a valid object');
869
+ return undefined;
870
+ }
871
+ const entries = Object.entries(parsed);
872
+ const invalidKey = entries.find(([, value]) => !isValidFlag(value));
873
+ if (invalidKey) {
874
+ logger?.warn(`Discarding cached flags due to invalid entry: ${invalidKey[0]}`);
875
+ return undefined;
876
+ }
877
+ const flags = entries.reduce((acc, [key, value]) => {
878
+ acc[key] = value;
879
+ return acc;
880
+ }, {});
881
+ return { flags, storageKey, fromLegacyKey };
882
+ }
883
+ catch (e) {
884
+ logger?.warn(`Could not parse cached flag evaluations from persistent storage: ${e.message}`);
885
+ return undefined;
886
+ }
887
+ }
888
+
680
889
  /**
681
890
  * An index for tracking the most recently used contexts by timestamp with the ability to
682
891
  * update entry timestamps and prune out least used contexts above a max capacity provided.
@@ -742,12 +951,18 @@ class ContextIndex {
742
951
  * This class handles persisting and loading flag values from a persistent
743
952
  * store. It intercepts updates and forwards them to the flag updater and
744
953
  * then persists changes after the updater has completed.
954
+ *
955
+ * Freshness metadata (timestamp + context attribute hash) is stored in a
956
+ * separate storage key (`{contextKey}_freshness`) alongside the flag data.
957
+ * Both keys are managed together — when a context is evicted, both the flag
958
+ * data and freshness record are cleared.
745
959
  */
746
960
  class FlagPersistence {
747
- constructor(_platform, _environmentNamespace, _maxCachedContexts, _flagStore, _flagUpdater, _logger, _timeStamper = () => Date.now()) {
961
+ constructor(_platform, _environmentNamespace, _maxCachedContexts, _disableCache, _flagStore, _flagUpdater, _logger, _timeStamper = () => Date.now()) {
748
962
  this._platform = _platform;
749
963
  this._environmentNamespace = _environmentNamespace;
750
964
  this._maxCachedContexts = _maxCachedContexts;
965
+ this._disableCache = _disableCache;
751
966
  this._flagStore = _flagStore;
752
967
  this._flagUpdater = _flagUpdater;
753
968
  this._logger = _logger;
@@ -779,35 +994,38 @@ class FlagPersistence {
779
994
  * {@link FlagUpdater} this {@link FlagPersistence} was constructed with.
780
995
  */
781
996
  async loadCached(context) {
782
- const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
783
- let flagsJson = await this._platform.storage?.get(storageKey);
784
- if (flagsJson === null || flagsJson === undefined) {
785
- // Fallback: in version <10.3.1 flag data was stored under the canonical key, check
786
- // to see if data is present and migrate the data if present.
787
- flagsJson = await this._platform.storage?.get(context.canonicalKey);
788
- if (flagsJson === null || flagsJson === undefined) {
789
- // return false indicating cache did not load if flag json is still absent
790
- return false;
791
- }
792
- // migrate data from version <10.3.1 and cleanup data that was under canonical key
793
- await this._platform.storage?.set(storageKey, flagsJson);
794
- await this._platform.storage?.clear(context.canonicalKey);
997
+ if (this._disableCache || this._maxCachedContexts <= 0) {
998
+ return false;
795
999
  }
796
- try {
797
- const flags = JSON.parse(flagsJson);
798
- // mapping flags to item descriptors
799
- const descriptors = Object.entries(flags).reduce((acc, [key, flag]) => {
800
- acc[key] = { version: flag.version, flag };
801
- return acc;
802
- }, {});
803
- this._flagUpdater.initCached(context, descriptors);
804
- this._logger.debug('Loaded cached flag evaluations from persistent storage');
805
- return true;
1000
+ if (!this._platform.storage) {
1001
+ return false;
806
1002
  }
807
- catch (e) {
808
- this._logger.warn(`Could not load cached flag evaluations from persistent storage: ${e.message}`);
1003
+ const cached = await loadCachedFlags(this._platform.storage, this._platform.crypto, this._environmentNamespace, context, this._logger);
1004
+ if (!cached) {
809
1005
  return false;
810
1006
  }
1007
+ // Migrate data from version <10.3.1 stored under the canonical key
1008
+ if (cached.fromLegacyKey) {
1009
+ await this._platform.storage.set(cached.storageKey, JSON.stringify(cached.flags));
1010
+ await this._platform.storage.clear(context.canonicalKey);
1011
+ }
1012
+ // mapping flags to item descriptors
1013
+ const descriptors = Object.entries(cached.flags).reduce((acc, [key, flag]) => {
1014
+ acc[key] = { version: flag.version, flag };
1015
+ return acc;
1016
+ }, {});
1017
+ this._flagUpdater.initCached(context, descriptors);
1018
+ this._logger.debug('Loaded cached flag evaluations from persistent storage');
1019
+ return true;
1020
+ }
1021
+ async _storeFreshness(contextStorageKey, context, timestamp) {
1022
+ const contextHash = await hashContext(this._platform.crypto, context);
1023
+ if (contextHash === undefined) {
1024
+ this._logger.error('Could not serialize context for freshness tracking');
1025
+ return;
1026
+ }
1027
+ const record = { timestamp, contextHash };
1028
+ await this._platform.storage?.set(`${contextStorageKey}${FRESHNESS_SUFFIX}`, JSON.stringify(record));
811
1029
  }
812
1030
  async _loadIndex() {
813
1031
  if (this._contextIndex !== undefined) {
@@ -829,13 +1047,26 @@ class FlagPersistence {
829
1047
  return this._contextIndex;
830
1048
  }
831
1049
  async _storeCache(context) {
1050
+ if (this._disableCache) {
1051
+ return;
1052
+ }
1053
+ const now = this._timeStamper();
832
1054
  const index = await this._loadIndex();
833
1055
  const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
834
- index.notice(storageKey, this._timeStamper());
1056
+ if (this._maxCachedContexts > 0) {
1057
+ index.notice(storageKey, now);
1058
+ }
835
1059
  const pruned = index.prune(this._maxCachedContexts);
836
- await Promise.all(pruned.map(async (it) => this._platform.storage?.clear(it.id)));
837
- // store index
1060
+ // If maxCachedContexts <= 0, current context was never added, so always skip flag write
1061
+ const currentContextWasPruned = this._maxCachedContexts <= 0 || pruned.some((it) => it.id === storageKey);
1062
+ await Promise.all(pruned.flatMap((it) => [
1063
+ this._platform.storage?.clear(it.id),
1064
+ this._platform.storage?.clear(`${it.id}${FRESHNESS_SUFFIX}`),
1065
+ ]));
838
1066
  await this._platform.storage?.set(await this._indexKeyPromise, index.toJson());
1067
+ if (currentContextWasPruned) {
1068
+ return;
1069
+ }
839
1070
  const allFlags = this._flagStore.getAll();
840
1071
  // mapping item descriptors to flags
841
1072
  const flags = Object.entries(allFlags).reduce((acc, [key, descriptor]) => {
@@ -845,8 +1076,15 @@ class FlagPersistence {
845
1076
  return acc;
846
1077
  }, {});
847
1078
  const jsonAll = JSON.stringify(flags);
848
- // store flag data
1079
+ // store flag data first, so freshness is never newer than the flags it describes
849
1080
  await this._platform.storage?.set(storageKey, jsonAll);
1081
+ // store freshness — best-effort, must not block flag persistence
1082
+ try {
1083
+ await this._storeFreshness(storageKey, context, now);
1084
+ }
1085
+ catch (e) {
1086
+ this._logger.warn(`Failed to store freshness data: ${e.message}`);
1087
+ }
850
1088
  }
851
1089
  }
852
1090
 
@@ -962,17 +1200,18 @@ class DefaultFlagManager {
962
1200
  * @param platform implementation of various platform provided functionality
963
1201
  * @param sdkKey that will be used to distinguish different environments
964
1202
  * @param maxCachedContexts that specifies the max number of contexts that will be cached in persistence
1203
+ * @param disableCache set to true to completely disable the persistent flag cache
965
1204
  * @param logger used for logging various messages
966
1205
  * @param timeStamper exists for testing purposes
967
1206
  */
968
- constructor(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
1207
+ constructor(platform, sdkKey, maxCachedContexts, disableCache, logger, timeStamper = () => Date.now()) {
969
1208
  this._flagStore = createDefaultFlagStore();
970
1209
  this._flagUpdater = createFlagUpdater(this._flagStore, logger);
971
- this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
1210
+ this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, disableCache, logger, timeStamper);
972
1211
  }
973
- async _initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
1212
+ async _initPersistence(platform, sdkKey, maxCachedContexts, disableCache, logger, timeStamper = () => Date.now()) {
974
1213
  const environmentNamespace = await namespaceForEnvironment(platform.crypto, sdkKey);
975
- return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this._flagStore, this._flagUpdater, logger, timeStamper);
1214
+ return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, disableCache, this._flagStore, this._flagUpdater, logger, timeStamper);
976
1215
  }
977
1216
  get(key) {
978
1217
  if (this._overrides && Object.prototype.hasOwnProperty.call(this._overrides, key)) {
@@ -1448,7 +1687,7 @@ class LDClientImpl {
1448
1687
  this._config = new ConfigurationImpl(options, internalOptions);
1449
1688
  this.logger = this._config.logger;
1450
1689
  this._baseHeaders = jsSdkCommon.defaultHeaders(this.sdkKey, this.platform.info, this._config.tags, this._config.serviceEndpoints.includeAuthorizationHeader, this._config.userAgentHeaderName);
1451
- this._flagManager = new DefaultFlagManager(this.platform, sdkKey, this._config.maxCachedContexts, this._config.logger);
1690
+ this._flagManager = new DefaultFlagManager(this.platform, sdkKey, this._config.maxCachedContexts, this._config.disableCache ?? false, this._config.logger);
1452
1691
  this._diagnosticsManager = createDiagnosticsManager(sdkKey, this._config, platform);
1453
1692
  this._eventProcessor = createEventProcessor(sdkKey, this._config, platform, this._baseHeaders, this._diagnosticsManager);
1454
1693
  this.emitter = new LDEmitter();
@@ -2542,16 +2781,88 @@ class BaseDataManager {
2542
2781
  }
2543
2782
  }
2544
2783
 
2784
+ function allConditionsMatch(conditions, input) {
2785
+ return Object.entries(conditions).every(([key, value]) => value === undefined || input[key] === value);
2786
+ }
2787
+ /**
2788
+ * Given a mode resolution table and the current input state, returns the
2789
+ * resolved FDv2 connection mode.
2790
+ *
2791
+ * Iterates entries in order. The first entry whose conditions all match the
2792
+ * input wins. If no entry matches (should not happen when the table ends with
2793
+ * a catch-all), falls back to `input.foregroundMode`.
2794
+ */
2795
+ const CONFIGURED_MODE_MAP = {
2796
+ foreground: 'foregroundMode',
2797
+ background: 'backgroundMode',
2798
+ };
2799
+ function resolveConnectionMode(table, input) {
2800
+ const match = table.find((entry) => allConditionsMatch(entry.conditions, input));
2801
+ if (match) {
2802
+ const { mode } = match;
2803
+ if (typeof mode === 'object') {
2804
+ return input[CONFIGURED_MODE_MAP[mode.configured]];
2805
+ }
2806
+ return mode;
2807
+ }
2808
+ return input.foregroundMode;
2809
+ }
2810
+ /**
2811
+ * Mode resolution table for mobile platforms (React Native, etc.).
2812
+ *
2813
+ * - No network → offline.
2814
+ * - Background → configured background mode.
2815
+ * - Foreground → configured foreground mode.
2816
+ */
2817
+ const MOBILE_TRANSITION_TABLE = [
2818
+ { conditions: { networkAvailable: false }, mode: 'offline' },
2819
+ { conditions: { lifecycle: 'background' }, mode: { configured: 'background' } },
2820
+ { conditions: { lifecycle: 'foreground' }, mode: { configured: 'foreground' } },
2821
+ ];
2822
+ /**
2823
+ * Mode resolution table for browser platforms.
2824
+ *
2825
+ * - No network → offline.
2826
+ * - Otherwise → configured foreground mode.
2827
+ *
2828
+ * Browser listener-driven streaming (auto-promotion to streaming when change
2829
+ * listeners are registered) is handled externally by the caller modifying
2830
+ * `foregroundMode` before consulting this table.
2831
+ */
2832
+ const BROWSER_TRANSITION_TABLE = [
2833
+ { conditions: { networkAvailable: false }, mode: 'offline' },
2834
+ { conditions: {}, mode: { configured: 'foreground' } },
2835
+ ];
2836
+ /**
2837
+ * Mode resolution table for desktop platforms (Electron, etc.).
2838
+ *
2839
+ * - No network → offline.
2840
+ * - Otherwise → configured foreground mode.
2841
+ */
2842
+ const DESKTOP_TRANSITION_TABLE = [
2843
+ { conditions: { networkAvailable: false }, mode: 'offline' },
2844
+ { conditions: {}, mode: { configured: 'foreground' } },
2845
+ ];
2846
+
2545
2847
  exports.platform = jsSdkCommon__namespace;
2848
+ exports.BROWSER_DATA_SYSTEM_DEFAULTS = BROWSER_DATA_SYSTEM_DEFAULTS;
2849
+ exports.BROWSER_TRANSITION_TABLE = BROWSER_TRANSITION_TABLE;
2546
2850
  exports.BaseDataManager = BaseDataManager;
2851
+ exports.DESKTOP_DATA_SYSTEM_DEFAULTS = DESKTOP_DATA_SYSTEM_DEFAULTS;
2852
+ exports.DESKTOP_TRANSITION_TABLE = DESKTOP_TRANSITION_TABLE;
2547
2853
  exports.DataSourceState = DataSourceState;
2548
2854
  exports.LDClientImpl = LDClientImpl;
2855
+ exports.MOBILE_DATA_SYSTEM_DEFAULTS = MOBILE_DATA_SYSTEM_DEFAULTS;
2856
+ exports.MOBILE_TRANSITION_TABLE = MOBILE_TRANSITION_TABLE;
2549
2857
  exports.browserFdv1Endpoints = browserFdv1Endpoints;
2858
+ exports.dataSystemValidators = dataSystemValidators;
2550
2859
  exports.fdv2Endpoints = fdv2Endpoints;
2551
2860
  exports.makeRequestor = makeRequestor;
2552
2861
  exports.mobileFdv1Endpoints = mobileFdv1Endpoints;
2553
2862
  exports.readFlagsFromBootstrap = readFlagsFromBootstrap;
2863
+ exports.resolveConnectionMode = resolveConnectionMode;
2554
2864
  exports.safeRegisterDebugOverridePlugins = safeRegisterDebugOverridePlugins;
2865
+ exports.validateOptions = validateOptions;
2555
2866
  Object.keys(jsSdkCommon).forEach(function (k) {
2556
2867
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2557
2868
  enumerable: true,