@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
@@ -1,4 +1,4 @@
1
- import { getPollingUri, TypeValidators, createSafeLogger, ServiceEndpoints, ApplicationTags, OptionMessages, NumberWithMinimum, SafeLogger, internal, deepCompact, clone, secondsToMillis, ClientContext, fastDeepEqual, defaultHeaders, Context, LDTimeoutError, AutoEnvAttributes, cancelableTimedPromise, LDClientError, base64UrlEncode, isHttpRecoverable, httpErrorMessage, LDPollingError, DataSourceErrorKind, getStreamingUri, shouldRetry, LDStreamingError } from '@launchdarkly/js-sdk-common';
1
+ import { getPollingUri, isNullish, TypeValidators, OptionMessages, NumberWithMinimum, createSafeLogger, ServiceEndpoints, ApplicationTags, SafeLogger, internal, deepCompact, clone, secondsToMillis, ClientContext, fastDeepEqual, defaultHeaders, Context, LDTimeoutError, AutoEnvAttributes, cancelableTimedPromise, LDClientError, base64UrlEncode, isHttpRecoverable, httpErrorMessage, LDPollingError, DataSourceErrorKind, getStreamingUri, shouldRetry, LDStreamingError } from '@launchdarkly/js-sdk-common';
2
2
  export * from '@launchdarkly/js-sdk-common';
3
3
  import * as jsSdkCommon from '@launchdarkly/js-sdk-common';
4
4
  export { jsSdkCommon as platform };
@@ -204,34 +204,208 @@ function createAsyncTaskQueue(logger) {
204
204
  };
205
205
  }
206
206
 
207
- // eslint-disable-next-line max-classes-per-file
208
- const validators = {
209
- logger: TypeValidators.Object,
210
- maxCachedContexts: TypeValidators.numberWithMin(0),
211
- baseUri: TypeValidators.String,
212
- streamUri: TypeValidators.String,
213
- eventsUri: TypeValidators.String,
214
- capacity: TypeValidators.numberWithMin(1),
215
- diagnosticRecordingInterval: TypeValidators.numberWithMin(2),
216
- flushInterval: TypeValidators.numberWithMin(2),
217
- streamInitialReconnectDelay: TypeValidators.numberWithMin(0),
218
- allAttributesPrivate: TypeValidators.Boolean,
219
- debug: TypeValidators.Boolean,
220
- diagnosticOptOut: TypeValidators.Boolean,
221
- withReasons: TypeValidators.Boolean,
222
- sendEvents: TypeValidators.Boolean,
207
+ function isCompoundValidator(v) {
208
+ return 'validate' in v;
209
+ }
210
+ /**
211
+ * Validates an options object against a map of validators and defaults.
212
+ *
213
+ * If `input` is null, undefined, or not an object the defaults are returned
214
+ * (with a warning for non-nullish non-objects).
215
+ *
216
+ * Supports special validator types created by:
217
+ * - {@link validatorOf}: recursively validates nested objects
218
+ * - {@link arrayOf}: validates arrays with per-item validation
219
+ * - {@link anyOf}: accepts the first matching validator from a list
220
+ * - {@link recordOf}: validates objects with dynamic keys
221
+ */
222
+ function validateOptions(input, validatorMap, defaults, logger, prefix) {
223
+ const result = { ...defaults };
224
+ if (isNullish(input)) {
225
+ return result;
226
+ }
227
+ if (!TypeValidators.Object.is(input)) {
228
+ logger?.warn(OptionMessages.wrongOptionType(prefix ?? 'config', 'object', typeof input));
229
+ return result;
230
+ }
231
+ Object.entries(input).forEach(([key, value]) => {
232
+ const validator = validatorMap[key];
233
+ const name = prefix ? `${prefix}.${key}` : key;
234
+ if (!validator) {
235
+ logger?.warn(OptionMessages.unknownOption(name));
236
+ return;
237
+ }
238
+ if (isNullish(value)) {
239
+ return;
240
+ }
241
+ if (isCompoundValidator(validator)) {
242
+ const validated = validator.validate(value, name, logger, defaults[key]);
243
+ if (validated !== undefined) {
244
+ result[key] = validated.value;
245
+ }
246
+ return;
247
+ }
248
+ if (validator.is(value)) {
249
+ result[key] = value;
250
+ return;
251
+ }
252
+ // Validation failed — apply correction or fall back to default.
253
+ const validatorType = validator.getType();
254
+ if (validatorType === 'boolean') {
255
+ logger?.warn(OptionMessages.wrongOptionTypeBoolean(name, typeof value));
256
+ result[key] = !!value;
257
+ }
258
+ else if (validatorType === 'boolean | undefined | null') {
259
+ logger?.warn(OptionMessages.wrongOptionTypeBoolean(name, typeof value));
260
+ if (typeof value !== 'boolean' && typeof value !== 'undefined' && value !== null) {
261
+ result[key] = !!value;
262
+ }
263
+ }
264
+ else if (validator instanceof NumberWithMinimum && TypeValidators.Number.is(value)) {
265
+ logger?.warn(OptionMessages.optionBelowMinimum(name, value, validator.min));
266
+ result[key] = validator.min;
267
+ }
268
+ else {
269
+ logger?.warn(OptionMessages.wrongOptionType(name, validatorType, typeof value));
270
+ }
271
+ });
272
+ return result;
273
+ }
274
+ /**
275
+ * Creates a validator for nested objects. When used in a validator map,
276
+ * `validateOptions` will recursively validate the nested object's properties.
277
+ * Defaults for nested fields are passed through from the parent.
278
+ */
279
+ function validatorOf(validators, builtInDefaults) {
280
+ return {
281
+ is: (u) => TypeValidators.Object.is(u),
282
+ getType: () => 'object',
283
+ validate(value, name, logger, defaults) {
284
+ if (!TypeValidators.Object.is(value)) {
285
+ logger?.warn(OptionMessages.wrongOptionType(name, 'object', typeof value));
286
+ return undefined;
287
+ }
288
+ const nestedDefaults = builtInDefaults ??
289
+ (TypeValidators.Object.is(defaults) ? defaults : {});
290
+ const nested = validateOptions(value, validators, nestedDefaults, logger, name);
291
+ return Object.keys(nested).length > 0 ? { value: nested } : undefined;
292
+ },
293
+ };
294
+ }
295
+ /**
296
+ * Creates a validator that tries each provided validator in order and uses the
297
+ * first one whose `is()` check passes. For compound validators the value is
298
+ * processed through `validate()`; for simple validators the value is accepted
299
+ * as-is. If no validator matches, a warning is logged and the default is
300
+ * preserved.
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * // Accepts either a boolean or a nested object with specific fields:
305
+ * anyOf(TypeValidators.Boolean, validatorOf({ lifecycle: TypeValidators.Boolean }))
306
+ * ```
307
+ */
308
+ function anyOf(...validators) {
309
+ return {
310
+ is: (u) => validators.some((v) => v.is(u)),
311
+ getType: () => validators.map((v) => v.getType()).join(' | '),
312
+ validate(value, name, logger, defaults) {
313
+ const match = validators.find((v) => v.is(value));
314
+ if (match) {
315
+ return isCompoundValidator(match)
316
+ ? match.validate(value, name, logger, defaults)
317
+ : { value };
318
+ }
319
+ logger?.warn(OptionMessages.wrongOptionType(name, this.getType(), typeof value));
320
+ return undefined;
321
+ },
322
+ };
323
+ }
324
+
325
+ const dataSourceTypeValidator = TypeValidators.oneOf('cache', 'polling', 'streaming');
326
+ const connectionModeValidator = TypeValidators.oneOf('streaming', 'polling', 'offline', 'one-shot', 'background');
327
+ const endpointValidators = {
328
+ pollingBaseUri: TypeValidators.String,
329
+ streamingBaseUri: TypeValidators.String,
330
+ };
331
+ ({
332
+ type: dataSourceTypeValidator,
223
333
  pollInterval: TypeValidators.numberWithMin(30),
224
- useReport: TypeValidators.Boolean,
225
- privateAttributes: TypeValidators.StringArray,
226
- applicationInfo: TypeValidators.Object,
227
- wrapperName: TypeValidators.String,
228
- wrapperVersion: TypeValidators.String,
229
- payloadFilterKey: TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
230
- hooks: TypeValidators.createTypeArray('Hook[]', {}),
231
- inspectors: TypeValidators.createTypeArray('LDInspection', {}),
232
- cleanOldPersistentData: TypeValidators.Boolean,
334
+ endpoints: validatorOf(endpointValidators),
335
+ });
336
+ ({
337
+ type: dataSourceTypeValidator,
338
+ initialReconnectDelay: TypeValidators.numberWithMin(1),
339
+ endpoints: validatorOf(endpointValidators),
340
+ });
341
+
342
+ const modeSwitchingValidators = {
343
+ lifecycle: TypeValidators.Boolean,
344
+ network: TypeValidators.Boolean,
345
+ };
346
+ const dataSystemValidators = {
347
+ initialConnectionMode: connectionModeValidator,
348
+ backgroundConnectionMode: connectionModeValidator,
349
+ automaticModeSwitching: anyOf(TypeValidators.Boolean, validatorOf(modeSwitchingValidators)),
350
+ };
351
+ /**
352
+ * Default FDv2 data system configuration for browser SDKs.
353
+ */
354
+ const BROWSER_DATA_SYSTEM_DEFAULTS = {
355
+ initialConnectionMode: 'one-shot',
356
+ backgroundConnectionMode: undefined,
357
+ automaticModeSwitching: false,
358
+ };
359
+ /**
360
+ * Default FDv2 data system configuration for mobile (React Native) SDKs.
361
+ */
362
+ const MOBILE_DATA_SYSTEM_DEFAULTS = {
363
+ initialConnectionMode: 'streaming',
364
+ backgroundConnectionMode: 'background',
365
+ automaticModeSwitching: true,
366
+ };
367
+ /**
368
+ * Default FDv2 data system configuration for desktop SDKs (Electron, etc.).
369
+ */
370
+ const DESKTOP_DATA_SYSTEM_DEFAULTS = {
371
+ initialConnectionMode: 'streaming',
372
+ backgroundConnectionMode: undefined,
373
+ automaticModeSwitching: false,
233
374
  };
234
375
 
376
+ function createValidators(options) {
377
+ return {
378
+ logger: TypeValidators.Object,
379
+ maxCachedContexts: TypeValidators.numberWithMin(0),
380
+ baseUri: TypeValidators.String,
381
+ streamUri: TypeValidators.String,
382
+ eventsUri: TypeValidators.String,
383
+ capacity: TypeValidators.numberWithMin(1),
384
+ diagnosticRecordingInterval: TypeValidators.numberWithMin(2),
385
+ flushInterval: TypeValidators.numberWithMin(2),
386
+ streamInitialReconnectDelay: TypeValidators.numberWithMin(0),
387
+ allAttributesPrivate: TypeValidators.Boolean,
388
+ debug: TypeValidators.Boolean,
389
+ diagnosticOptOut: TypeValidators.Boolean,
390
+ withReasons: TypeValidators.Boolean,
391
+ sendEvents: TypeValidators.Boolean,
392
+ pollInterval: TypeValidators.numberWithMin(30),
393
+ useReport: TypeValidators.Boolean,
394
+ privateAttributes: TypeValidators.StringArray,
395
+ disableCache: TypeValidators.Boolean,
396
+ applicationInfo: TypeValidators.Object,
397
+ wrapperName: TypeValidators.String,
398
+ wrapperVersion: TypeValidators.String,
399
+ payloadFilterKey: TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
400
+ hooks: TypeValidators.createTypeArray('Hook[]', {}),
401
+ inspectors: TypeValidators.createTypeArray('LDInspection', {}),
402
+ cleanOldPersistentData: TypeValidators.Boolean,
403
+ dataSystem: options?.dataSystemDefaults
404
+ ? validatorOf(dataSystemValidators, options.dataSystemDefaults)
405
+ : TypeValidators.Object,
406
+ };
407
+ }
408
+
235
409
  const DEFAULT_POLLING_INTERVAL = 60 * 5;
236
410
  const DEFAULT_POLLING = 'https://clientsdk.launchdarkly.com';
237
411
  const DEFAULT_STREAM = 'https://clientstream.launchdarkly.com';
@@ -257,6 +431,7 @@ class ConfigurationImpl {
257
431
  // eslint-disable-next-line @typescript-eslint/naming-convention
258
432
  this.streamUri = DEFAULT_STREAM;
259
433
  this.maxCachedContexts = 5;
434
+ this.disableCache = false;
260
435
  this.capacity = 100;
261
436
  this.diagnosticRecordingInterval = 900;
262
437
  this.flushInterval = 30;
@@ -273,8 +448,15 @@ class ConfigurationImpl {
273
448
  this.hooks = [];
274
449
  this.inspectors = [];
275
450
  this.logger = ensureSafeLogger(pristineOptions.logger);
276
- const errors = this._validateTypesAndNames(pristineOptions);
277
- errors.forEach((e) => this.logger.warn(e));
451
+ const validators = createValidators({
452
+ dataSystemDefaults: internalOptions.dataSystemDefaults,
453
+ });
454
+ const validated = validateOptions(pristineOptions, validators, {}, this.logger);
455
+ Object.entries(validated).forEach(([k, v]) => {
456
+ if (k !== 'logger') {
457
+ this[k] = v;
458
+ }
459
+ });
278
460
  this.serviceEndpoints = new ServiceEndpoints(this.streamUri, this.baseUri, this.eventsUri, internalOptions.analyticsEventPath, internalOptions.diagnosticEventPath, internalOptions.includeAuthorizationHeader, pristineOptions.payloadFilterKey);
279
461
  this.useReport = pristineOptions.useReport ?? false;
280
462
  this.tags = new ApplicationTags({ application: this.applicationInfo, logger: this.logger });
@@ -283,44 +465,6 @@ class ConfigurationImpl {
283
465
  this.credentialType = internalOptions.credentialType;
284
466
  this.getImplementationHooks = internalOptions.getImplementationHooks;
285
467
  }
286
- _validateTypesAndNames(pristineOptions) {
287
- const errors = [];
288
- Object.entries(pristineOptions).forEach(([k, v]) => {
289
- const validator = validators[k];
290
- if (validator) {
291
- if (!validator.is(v)) {
292
- const validatorType = validator.getType();
293
- if (validatorType === 'boolean') {
294
- errors.push(OptionMessages.wrongOptionTypeBoolean(k, typeof v));
295
- this[k] = !!v;
296
- }
297
- else if (validatorType === 'boolean | undefined | null') {
298
- errors.push(OptionMessages.wrongOptionTypeBoolean(k, typeof v));
299
- if (typeof v !== 'boolean' && typeof v !== 'undefined' && v !== null) {
300
- this[k] = !!v;
301
- }
302
- }
303
- else if (validator instanceof NumberWithMinimum && TypeValidators.Number.is(v)) {
304
- const { min } = validator;
305
- errors.push(OptionMessages.optionBelowMinimum(k, v, min));
306
- this[k] = min;
307
- }
308
- else {
309
- errors.push(OptionMessages.wrongOptionType(k, validator.getType(), typeof v));
310
- }
311
- }
312
- else if (k === 'logger') ;
313
- else {
314
- // if an option is explicitly null, coerce to undefined
315
- this[k] = v ?? undefined;
316
- }
317
- }
318
- else {
319
- errors.push(OptionMessages.unknownOption(k));
320
- }
321
- });
322
- return errors;
323
- }
324
468
  }
325
469
 
326
470
  async function digest(hasher, encoding) {
@@ -659,6 +803,71 @@ class EventFactory extends internal.EventFactoryBase {
659
803
  }
660
804
  }
661
805
 
806
+ /**
807
+ * Suffix appended to context storage keys to form the freshness storage key.
808
+ */
809
+ const FRESHNESS_SUFFIX = '_freshness';
810
+ /**
811
+ * Computes a SHA-256 hash of the context's full canonical JSON.
812
+ * Returns `undefined` if the context cannot be serialized.
813
+ */
814
+ async function hashContext(crypto, context) {
815
+ const json = context.canonicalUnfilteredJson();
816
+ if (!json) {
817
+ return undefined;
818
+ }
819
+ return digest(crypto.createHash('sha256').update(json), 'base64');
820
+ }
821
+
822
+ function isValidFlag(value) {
823
+ return value !== null && typeof value === 'object' && typeof value.version === 'number';
824
+ }
825
+ /**
826
+ * Loads cached flag data from storage for the given context.
827
+ *
828
+ * Checks the current storage key first, then falls back to the legacy
829
+ * canonical key location (pre-10.3.1). Does NOT perform migration — the
830
+ * caller is responsible for migrating data if {@link CachedFlagData.fromLegacyKey}
831
+ * is true.
832
+ *
833
+ * @returns The cached flag data, or `undefined` on cache miss or parse error.
834
+ */
835
+ async function loadCachedFlags(storage, crypto, environmentNamespace, context, logger) {
836
+ const storageKey = await namespaceForContextData(crypto, environmentNamespace, context);
837
+ let flagsJson = await storage.get(storageKey);
838
+ let fromLegacyKey = false;
839
+ if (flagsJson === null || flagsJson === undefined) {
840
+ // Fallback: in version <10.3.1 flag data was stored under the canonical key.
841
+ flagsJson = await storage.get(context.canonicalKey);
842
+ if (flagsJson === null || flagsJson === undefined) {
843
+ return undefined;
844
+ }
845
+ fromLegacyKey = true;
846
+ }
847
+ try {
848
+ const parsed = JSON.parse(flagsJson);
849
+ if (parsed === null || typeof parsed !== 'object') {
850
+ logger?.warn('Cached flag data is not a valid object');
851
+ return undefined;
852
+ }
853
+ const entries = Object.entries(parsed);
854
+ const invalidKey = entries.find(([, value]) => !isValidFlag(value));
855
+ if (invalidKey) {
856
+ logger?.warn(`Discarding cached flags due to invalid entry: ${invalidKey[0]}`);
857
+ return undefined;
858
+ }
859
+ const flags = entries.reduce((acc, [key, value]) => {
860
+ acc[key] = value;
861
+ return acc;
862
+ }, {});
863
+ return { flags, storageKey, fromLegacyKey };
864
+ }
865
+ catch (e) {
866
+ logger?.warn(`Could not parse cached flag evaluations from persistent storage: ${e.message}`);
867
+ return undefined;
868
+ }
869
+ }
870
+
662
871
  /**
663
872
  * An index for tracking the most recently used contexts by timestamp with the ability to
664
873
  * update entry timestamps and prune out least used contexts above a max capacity provided.
@@ -724,12 +933,18 @@ class ContextIndex {
724
933
  * This class handles persisting and loading flag values from a persistent
725
934
  * store. It intercepts updates and forwards them to the flag updater and
726
935
  * then persists changes after the updater has completed.
936
+ *
937
+ * Freshness metadata (timestamp + context attribute hash) is stored in a
938
+ * separate storage key (`{contextKey}_freshness`) alongside the flag data.
939
+ * Both keys are managed together — when a context is evicted, both the flag
940
+ * data and freshness record are cleared.
727
941
  */
728
942
  class FlagPersistence {
729
- constructor(_platform, _environmentNamespace, _maxCachedContexts, _flagStore, _flagUpdater, _logger, _timeStamper = () => Date.now()) {
943
+ constructor(_platform, _environmentNamespace, _maxCachedContexts, _disableCache, _flagStore, _flagUpdater, _logger, _timeStamper = () => Date.now()) {
730
944
  this._platform = _platform;
731
945
  this._environmentNamespace = _environmentNamespace;
732
946
  this._maxCachedContexts = _maxCachedContexts;
947
+ this._disableCache = _disableCache;
733
948
  this._flagStore = _flagStore;
734
949
  this._flagUpdater = _flagUpdater;
735
950
  this._logger = _logger;
@@ -761,35 +976,38 @@ class FlagPersistence {
761
976
  * {@link FlagUpdater} this {@link FlagPersistence} was constructed with.
762
977
  */
763
978
  async loadCached(context) {
764
- const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
765
- let flagsJson = await this._platform.storage?.get(storageKey);
766
- if (flagsJson === null || flagsJson === undefined) {
767
- // Fallback: in version <10.3.1 flag data was stored under the canonical key, check
768
- // to see if data is present and migrate the data if present.
769
- flagsJson = await this._platform.storage?.get(context.canonicalKey);
770
- if (flagsJson === null || flagsJson === undefined) {
771
- // return false indicating cache did not load if flag json is still absent
772
- return false;
773
- }
774
- // migrate data from version <10.3.1 and cleanup data that was under canonical key
775
- await this._platform.storage?.set(storageKey, flagsJson);
776
- await this._platform.storage?.clear(context.canonicalKey);
979
+ if (this._disableCache || this._maxCachedContexts <= 0) {
980
+ return false;
777
981
  }
778
- try {
779
- const flags = JSON.parse(flagsJson);
780
- // mapping flags to item descriptors
781
- const descriptors = Object.entries(flags).reduce((acc, [key, flag]) => {
782
- acc[key] = { version: flag.version, flag };
783
- return acc;
784
- }, {});
785
- this._flagUpdater.initCached(context, descriptors);
786
- this._logger.debug('Loaded cached flag evaluations from persistent storage');
787
- return true;
982
+ if (!this._platform.storage) {
983
+ return false;
788
984
  }
789
- catch (e) {
790
- this._logger.warn(`Could not load cached flag evaluations from persistent storage: ${e.message}`);
985
+ const cached = await loadCachedFlags(this._platform.storage, this._platform.crypto, this._environmentNamespace, context, this._logger);
986
+ if (!cached) {
791
987
  return false;
792
988
  }
989
+ // Migrate data from version <10.3.1 stored under the canonical key
990
+ if (cached.fromLegacyKey) {
991
+ await this._platform.storage.set(cached.storageKey, JSON.stringify(cached.flags));
992
+ await this._platform.storage.clear(context.canonicalKey);
993
+ }
994
+ // mapping flags to item descriptors
995
+ const descriptors = Object.entries(cached.flags).reduce((acc, [key, flag]) => {
996
+ acc[key] = { version: flag.version, flag };
997
+ return acc;
998
+ }, {});
999
+ this._flagUpdater.initCached(context, descriptors);
1000
+ this._logger.debug('Loaded cached flag evaluations from persistent storage');
1001
+ return true;
1002
+ }
1003
+ async _storeFreshness(contextStorageKey, context, timestamp) {
1004
+ const contextHash = await hashContext(this._platform.crypto, context);
1005
+ if (contextHash === undefined) {
1006
+ this._logger.error('Could not serialize context for freshness tracking');
1007
+ return;
1008
+ }
1009
+ const record = { timestamp, contextHash };
1010
+ await this._platform.storage?.set(`${contextStorageKey}${FRESHNESS_SUFFIX}`, JSON.stringify(record));
793
1011
  }
794
1012
  async _loadIndex() {
795
1013
  if (this._contextIndex !== undefined) {
@@ -811,13 +1029,26 @@ class FlagPersistence {
811
1029
  return this._contextIndex;
812
1030
  }
813
1031
  async _storeCache(context) {
1032
+ if (this._disableCache) {
1033
+ return;
1034
+ }
1035
+ const now = this._timeStamper();
814
1036
  const index = await this._loadIndex();
815
1037
  const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
816
- index.notice(storageKey, this._timeStamper());
1038
+ if (this._maxCachedContexts > 0) {
1039
+ index.notice(storageKey, now);
1040
+ }
817
1041
  const pruned = index.prune(this._maxCachedContexts);
818
- await Promise.all(pruned.map(async (it) => this._platform.storage?.clear(it.id)));
819
- // store index
1042
+ // If maxCachedContexts <= 0, current context was never added, so always skip flag write
1043
+ const currentContextWasPruned = this._maxCachedContexts <= 0 || pruned.some((it) => it.id === storageKey);
1044
+ await Promise.all(pruned.flatMap((it) => [
1045
+ this._platform.storage?.clear(it.id),
1046
+ this._platform.storage?.clear(`${it.id}${FRESHNESS_SUFFIX}`),
1047
+ ]));
820
1048
  await this._platform.storage?.set(await this._indexKeyPromise, index.toJson());
1049
+ if (currentContextWasPruned) {
1050
+ return;
1051
+ }
821
1052
  const allFlags = this._flagStore.getAll();
822
1053
  // mapping item descriptors to flags
823
1054
  const flags = Object.entries(allFlags).reduce((acc, [key, descriptor]) => {
@@ -827,8 +1058,15 @@ class FlagPersistence {
827
1058
  return acc;
828
1059
  }, {});
829
1060
  const jsonAll = JSON.stringify(flags);
830
- // store flag data
1061
+ // store flag data first, so freshness is never newer than the flags it describes
831
1062
  await this._platform.storage?.set(storageKey, jsonAll);
1063
+ // store freshness — best-effort, must not block flag persistence
1064
+ try {
1065
+ await this._storeFreshness(storageKey, context, now);
1066
+ }
1067
+ catch (e) {
1068
+ this._logger.warn(`Failed to store freshness data: ${e.message}`);
1069
+ }
832
1070
  }
833
1071
  }
834
1072
 
@@ -944,17 +1182,18 @@ class DefaultFlagManager {
944
1182
  * @param platform implementation of various platform provided functionality
945
1183
  * @param sdkKey that will be used to distinguish different environments
946
1184
  * @param maxCachedContexts that specifies the max number of contexts that will be cached in persistence
1185
+ * @param disableCache set to true to completely disable the persistent flag cache
947
1186
  * @param logger used for logging various messages
948
1187
  * @param timeStamper exists for testing purposes
949
1188
  */
950
- constructor(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
1189
+ constructor(platform, sdkKey, maxCachedContexts, disableCache, logger, timeStamper = () => Date.now()) {
951
1190
  this._flagStore = createDefaultFlagStore();
952
1191
  this._flagUpdater = createFlagUpdater(this._flagStore, logger);
953
- this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
1192
+ this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, disableCache, logger, timeStamper);
954
1193
  }
955
- async _initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
1194
+ async _initPersistence(platform, sdkKey, maxCachedContexts, disableCache, logger, timeStamper = () => Date.now()) {
956
1195
  const environmentNamespace = await namespaceForEnvironment(platform.crypto, sdkKey);
957
- return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this._flagStore, this._flagUpdater, logger, timeStamper);
1196
+ return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, disableCache, this._flagStore, this._flagUpdater, logger, timeStamper);
958
1197
  }
959
1198
  get(key) {
960
1199
  if (this._overrides && Object.prototype.hasOwnProperty.call(this._overrides, key)) {
@@ -1430,7 +1669,7 @@ class LDClientImpl {
1430
1669
  this._config = new ConfigurationImpl(options, internalOptions);
1431
1670
  this.logger = this._config.logger;
1432
1671
  this._baseHeaders = defaultHeaders(this.sdkKey, this.platform.info, this._config.tags, this._config.serviceEndpoints.includeAuthorizationHeader, this._config.userAgentHeaderName);
1433
- this._flagManager = new DefaultFlagManager(this.platform, sdkKey, this._config.maxCachedContexts, this._config.logger);
1672
+ this._flagManager = new DefaultFlagManager(this.platform, sdkKey, this._config.maxCachedContexts, this._config.disableCache ?? false, this._config.logger);
1434
1673
  this._diagnosticsManager = createDiagnosticsManager(sdkKey, this._config, platform);
1435
1674
  this._eventProcessor = createEventProcessor(sdkKey, this._config, platform, this._baseHeaders, this._diagnosticsManager);
1436
1675
  this.emitter = new LDEmitter();
@@ -2524,5 +2763,68 @@ class BaseDataManager {
2524
2763
  }
2525
2764
  }
2526
2765
 
2527
- export { BaseDataManager, DataSourceState, LDClientImpl, browserFdv1Endpoints, fdv2Endpoints, makeRequestor, mobileFdv1Endpoints, readFlagsFromBootstrap, safeRegisterDebugOverridePlugins };
2766
+ function allConditionsMatch(conditions, input) {
2767
+ return Object.entries(conditions).every(([key, value]) => value === undefined || input[key] === value);
2768
+ }
2769
+ /**
2770
+ * Given a mode resolution table and the current input state, returns the
2771
+ * resolved FDv2 connection mode.
2772
+ *
2773
+ * Iterates entries in order. The first entry whose conditions all match the
2774
+ * input wins. If no entry matches (should not happen when the table ends with
2775
+ * a catch-all), falls back to `input.foregroundMode`.
2776
+ */
2777
+ const CONFIGURED_MODE_MAP = {
2778
+ foreground: 'foregroundMode',
2779
+ background: 'backgroundMode',
2780
+ };
2781
+ function resolveConnectionMode(table, input) {
2782
+ const match = table.find((entry) => allConditionsMatch(entry.conditions, input));
2783
+ if (match) {
2784
+ const { mode } = match;
2785
+ if (typeof mode === 'object') {
2786
+ return input[CONFIGURED_MODE_MAP[mode.configured]];
2787
+ }
2788
+ return mode;
2789
+ }
2790
+ return input.foregroundMode;
2791
+ }
2792
+ /**
2793
+ * Mode resolution table for mobile platforms (React Native, etc.).
2794
+ *
2795
+ * - No network → offline.
2796
+ * - Background → configured background mode.
2797
+ * - Foreground → configured foreground mode.
2798
+ */
2799
+ const MOBILE_TRANSITION_TABLE = [
2800
+ { conditions: { networkAvailable: false }, mode: 'offline' },
2801
+ { conditions: { lifecycle: 'background' }, mode: { configured: 'background' } },
2802
+ { conditions: { lifecycle: 'foreground' }, mode: { configured: 'foreground' } },
2803
+ ];
2804
+ /**
2805
+ * Mode resolution table for browser platforms.
2806
+ *
2807
+ * - No network → offline.
2808
+ * - Otherwise → configured foreground mode.
2809
+ *
2810
+ * Browser listener-driven streaming (auto-promotion to streaming when change
2811
+ * listeners are registered) is handled externally by the caller modifying
2812
+ * `foregroundMode` before consulting this table.
2813
+ */
2814
+ const BROWSER_TRANSITION_TABLE = [
2815
+ { conditions: { networkAvailable: false }, mode: 'offline' },
2816
+ { conditions: {}, mode: { configured: 'foreground' } },
2817
+ ];
2818
+ /**
2819
+ * Mode resolution table for desktop platforms (Electron, etc.).
2820
+ *
2821
+ * - No network → offline.
2822
+ * - Otherwise → configured foreground mode.
2823
+ */
2824
+ const DESKTOP_TRANSITION_TABLE = [
2825
+ { conditions: { networkAvailable: false }, mode: 'offline' },
2826
+ { conditions: {}, mode: { configured: 'foreground' } },
2827
+ ];
2828
+
2829
+ export { BROWSER_DATA_SYSTEM_DEFAULTS, BROWSER_TRANSITION_TABLE, BaseDataManager, DESKTOP_DATA_SYSTEM_DEFAULTS, DESKTOP_TRANSITION_TABLE, DataSourceState, LDClientImpl, MOBILE_DATA_SYSTEM_DEFAULTS, MOBILE_TRANSITION_TABLE, browserFdv1Endpoints, dataSystemValidators, fdv2Endpoints, makeRequestor, mobileFdv1Endpoints, readFlagsFromBootstrap, resolveConnectionMode, safeRegisterDebugOverridePlugins, validateOptions };
2528
2830
  //# sourceMappingURL=index.mjs.map