superposition-provider 0.104.0 → 0.106.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/dist/index.js CHANGED
@@ -3183,7 +3183,7 @@ var runtimeConfig = {};
3183
3183
 
3184
3184
  var name = "superposition-sdk";
3185
3185
  var description = "superposition-sdk client";
3186
- var version = "0.104.0";
3186
+ var version = "0.106.0";
3187
3187
  var repository = {
3188
3188
  type: "git",
3189
3189
  url: "git+https://github.com/juspay/superposition.git"
@@ -16392,13 +16392,14 @@ function requireModels () {
16392
16392
  } (superpositionSdk));
16393
16393
 
16394
16394
  class ExperimentationClient {
16395
- constructor(superpositionOptions, experimentOptions) {
16395
+ constructor(superpositionOptions, experimentOptions, onExperimentsChange) {
16396
16396
  this.superpositionOptions = superpositionOptions;
16397
16397
  this.cachedExperiments = null;
16398
16398
  this.cachedExperimentGroups = null;
16399
16399
  this.lastUpdated = null;
16400
16400
  this.evaluationCache = new Map();
16401
16401
  this.options = experimentOptions;
16402
+ this.onExperimentsChange = onExperimentsChange;
16402
16403
  this.smithyClient = new superpositionSdk.SuperpositionClient({
16403
16404
  endpoint: superpositionOptions.endpoint,
16404
16405
  token: { token: superpositionOptions.token },
@@ -16412,6 +16413,7 @@ class ExperimentationClient {
16412
16413
  if (experiments && experimentgroups) {
16413
16414
  this.cachedExperiments = experiments;
16414
16415
  this.cachedExperimentGroups = experimentgroups;
16416
+ this.onExperimentsChange?.();
16415
16417
  this.lastUpdated = new Date();
16416
16418
  console.log("Experiments and Experiment Groups fetched successfully.");
16417
16419
  }
@@ -16423,21 +16425,28 @@ class ExperimentationClient {
16423
16425
  }
16424
16426
  }
16425
16427
  startPolling(interval) {
16426
- this.pollingInterval = setInterval(async () => {
16428
+ const weakSelf = new WeakRef(this);
16429
+ const poll = async () => {
16430
+ const self = weakSelf.deref();
16431
+ if (!self)
16432
+ return;
16427
16433
  try {
16428
- const experiments = await this.fetchExperiments();
16429
- const experimentGroups = await this.fetchExperimentGroups();
16434
+ const experiments = await self.fetchExperiments();
16435
+ const experimentGroups = await self.fetchExperimentGroups();
16430
16436
  if (experiments && experimentGroups) {
16431
- this.cachedExperiments = experiments;
16432
- this.cachedExperimentGroups = experimentGroups;
16433
- this.lastUpdated = new Date();
16437
+ self.cachedExperiments = experiments;
16438
+ self.cachedExperimentGroups = experimentGroups;
16439
+ self.lastUpdated = new Date();
16434
16440
  console.log("Experiments and Experiment Groups refreshed successfully.");
16441
+ self.onExperimentsChange?.();
16435
16442
  }
16436
16443
  }
16437
16444
  catch (error) {
16438
16445
  console.error("Polling error:", error);
16439
16446
  }
16440
- }, interval);
16447
+ self.pollingInterval = setTimeout(poll, interval);
16448
+ };
16449
+ this.pollingInterval = setTimeout(poll, interval);
16441
16450
  }
16442
16451
  async fetchExperiments() {
16443
16452
  try {
@@ -16490,7 +16499,7 @@ class ExperimentationClient {
16490
16499
  id: exp.id,
16491
16500
  context: this.normalizeToStringRecord(exp.context),
16492
16501
  variants: variants,
16493
- traffic_percentage: exp.traffic_percentage || 100,
16502
+ traffic_percentage: exp.traffic_percentage ?? 100,
16494
16503
  status: exp.status || superpositionSdk.ExperimentStatusType.DISCARDED,
16495
16504
  });
16496
16505
  }
@@ -16524,7 +16533,7 @@ class ExperimentationClient {
16524
16533
  experimentGroups.push({
16525
16534
  id: exp_group.id,
16526
16535
  context: this.normalizeToStringRecord(exp_group.context),
16527
- traffic_percentage: exp_group.traffic_percentage || 100,
16536
+ traffic_percentage: exp_group.traffic_percentage ?? 100,
16528
16537
  member_experiment_ids: exp_group.member_experiment_ids || [],
16529
16538
  group_type: exp_group.group_type ||
16530
16539
  superpositionSdk.GroupType.USER_CREATED,
@@ -16583,6 +16592,7 @@ class ExperimentationClient {
16583
16592
  if (experiments) {
16584
16593
  this.cachedExperiments = experiments;
16585
16594
  this.lastUpdated = new Date();
16595
+ this.onExperimentsChange?.();
16586
16596
  }
16587
16597
  }
16588
16598
  catch (error) {
@@ -16600,6 +16610,7 @@ class ExperimentationClient {
16600
16610
  const experiments = await this.fetchExperiments();
16601
16611
  if (experiments) {
16602
16612
  this.cachedExperiments = experiments;
16613
+ this.onExperimentsChange?.();
16603
16614
  this.lastUpdated = new Date();
16604
16615
  }
16605
16616
  }
@@ -16620,6 +16631,7 @@ class ExperimentationClient {
16620
16631
  if (experimentGroups) {
16621
16632
  this.cachedExperimentGroups = experimentGroups;
16622
16633
  this.lastUpdated = new Date();
16634
+ this.onExperimentsChange?.();
16623
16635
  }
16624
16636
  }
16625
16637
  catch (error) {
@@ -16637,11 +16649,18 @@ class ExperimentationClient {
16637
16649
  const experimentGroups = await this.fetchExperimentGroups();
16638
16650
  if (experimentGroups) {
16639
16651
  this.cachedExperimentGroups = experimentGroups;
16652
+ this.onExperimentsChange?.();
16640
16653
  this.lastUpdated = new Date();
16641
16654
  }
16642
16655
  }
16643
16656
  return this.cachedExperimentGroups || [];
16644
16657
  }
16658
+ getCachedExperiments() {
16659
+ return this.cachedExperiments;
16660
+ }
16661
+ getCachedExperimentGroups() {
16662
+ return this.cachedExperimentGroups;
16663
+ }
16645
16664
  generateCacheKey(queryData) {
16646
16665
  return JSON.stringify(queryData, Object.keys(queryData).sort());
16647
16666
  }
@@ -16657,11 +16676,12 @@ class ExperimentationClient {
16657
16676
  async close() {
16658
16677
  try {
16659
16678
  if (this.pollingInterval) {
16660
- clearInterval(this.pollingInterval);
16679
+ clearTimeout(this.pollingInterval);
16661
16680
  console.log("Polling stopped successfully");
16662
16681
  }
16663
16682
  this.clearEvalCache();
16664
16683
  this.cachedExperiments = null;
16684
+ this.cachedExperimentGroups = null;
16665
16685
  this.lastUpdated = null;
16666
16686
  console.log("ExperimentationClient closed successfully");
16667
16687
  }
@@ -16672,30 +16692,47 @@ class ExperimentationClient {
16672
16692
  }
16673
16693
  }
16674
16694
 
16695
+ const _cacheRegistry = new FinalizationRegistry((freeFn) => {
16696
+ setImmediate(freeFn);
16697
+ });
16675
16698
  class ConfigurationClient {
16676
16699
  constructor(config, resolver, options = {}, experimentationOptions) {
16677
16700
  this.currentConfigData = null;
16701
+ this.providerCache = null;
16702
+ this.refreshInterval = null;
16678
16703
  this.defaults = null;
16679
16704
  this.config = config;
16680
16705
  this.resolver = resolver;
16681
16706
  this.options = options;
16707
+ this.providerCache = resolver.createProviderCache();
16708
+ const cache = this.providerCache;
16709
+ _cacheRegistry.register(this, () => {
16710
+ cache.free();
16711
+ }, this);
16682
16712
  if (this.options.refreshStrategy &&
16683
16713
  "interval" in this.options.refreshStrategy) {
16684
16714
  const strategy = this.options.refreshStrategy;
16685
- setInterval(async () => {
16715
+ const weakSelf = new WeakRef(this);
16716
+ const poll = async () => {
16717
+ const self = weakSelf.deref();
16718
+ if (!self)
16719
+ return;
16686
16720
  try {
16687
- const refreshedConfig = await this.fetchConfigData();
16688
- this.currentConfigData = refreshedConfig;
16721
+ const refreshedConfig = await self.fetchConfigData();
16722
+ self.currentConfigData = refreshedConfig;
16723
+ self.providerCache?.initConfig(refreshedConfig.default_configs, refreshedConfig.contexts, refreshedConfig.overrides, refreshedConfig.dimensions);
16689
16724
  console.log("Configuration refreshed successfully.");
16690
16725
  }
16691
16726
  catch (error) {
16692
16727
  console.error("Failed to refresh configuration. Will continue to use the last known good configuration.", error);
16693
16728
  }
16694
- }, strategy.interval);
16729
+ self.refreshInterval = setTimeout(poll, strategy.interval);
16730
+ };
16731
+ this.refreshInterval = setTimeout(poll, strategy.interval);
16695
16732
  }
16696
16733
  if (experimentationOptions) {
16697
16734
  this.experimentationOptions = experimentationOptions;
16698
- this.experimentationClient = new ExperimentationClient(config, experimentationOptions);
16735
+ this.experimentationClient = new ExperimentationClient(config, experimentationOptions, this.reinitExperimentsCache.bind(this));
16699
16736
  }
16700
16737
  this.smithyClient = new superpositionSdk.SuperpositionClient({
16701
16738
  endpoint: this.config.endpoint,
@@ -16704,26 +16741,35 @@ class ConfigurationClient {
16704
16741
  });
16705
16742
  }
16706
16743
  async initialize() {
16707
- // Initialize experimentation client if present
16744
+ const configData = await this.fetchConfigData();
16745
+ this.providerCache.initConfig(configData.default_configs, configData.contexts, configData.overrides, configData.dimensions);
16708
16746
  if (this.experimentationClient) {
16709
16747
  await this.experimentationClient.initialize();
16748
+ this.reinitExperimentsCache();
16749
+ }
16750
+ }
16751
+ reinitExperimentsCache() {
16752
+ if (!this.experimentationClient || !this.providerCache)
16753
+ return;
16754
+ const experiments = this.experimentationClient.getCachedExperiments();
16755
+ const experimentGroups = this.experimentationClient.getCachedExperimentGroups();
16756
+ if (experiments && experimentGroups) {
16757
+ this.providerCache.initExperiments(experiments, experimentGroups);
16710
16758
  }
16711
16759
  }
16712
16760
  async eval(queryData, filterPrefixes, targetingKey) {
16713
16761
  try {
16714
- const configData = this.currentConfigData || await this.fetchConfigData();
16715
- let experimentationArgs;
16716
- if (this.experimentationClient && targetingKey) {
16717
- const experiments = await this.experimentationClient.getExperiments();
16718
- const experiment_groups = await this.experimentationClient.getExperimentGroups();
16719
- experimentationArgs = {
16720
- experiments,
16721
- experiment_groups,
16722
- targeting_key: targetingKey,
16723
- };
16762
+ if (!this.currentConfigData) {
16763
+ const configData = await this.fetchConfigData();
16764
+ this.providerCache.initConfig(configData.default_configs, configData.contexts, configData.overrides, configData.dimensions);
16724
16765
  }
16725
- const result = this.resolver.resolveConfig(configData.default_configs || {}, configData.contexts || [], configData.overrides || {}, configData.dimensions || {}, queryData, "merge", filterPrefixes, experimentationArgs);
16726
- return result;
16766
+ if (this.experimentationClient && targetingKey &&
16767
+ !this.experimentationClient.getCachedExperiments()) {
16768
+ await this.experimentationClient.getExperiments();
16769
+ await this.experimentationClient.getExperimentGroups();
16770
+ this.reinitExperimentsCache();
16771
+ }
16772
+ return this.providerCache.evalConfig(queryData, "merge", filterPrefixes, targetingKey);
16727
16773
  }
16728
16774
  catch (error) {
16729
16775
  if (this.defaults) {
@@ -16763,21 +16809,17 @@ class ConfigurationClient {
16763
16809
  // TODO: Remove this function all together and use eval for getAllConfigValue as well
16764
16810
  async getAllConfigValue(defaultValue, context, targetingKey) {
16765
16811
  try {
16766
- const configData = this.currentConfigData || await this.fetchConfigData();
16767
- // Prepare query data with experiment variants if applicable
16768
- let queryData = { ...context };
16769
- if (this.experimentationClient && targetingKey) {
16770
- const experiments = await this.experimentationClient.getExperiments();
16771
- const experiment_groups = await this.experimentationClient.getExperimentGroups();
16772
- const identifier = this.experimentationOptions?.defaultIdentifier ||
16773
- (targetingKey ? targetingKey : "default");
16774
- const variantIds = await this.getApplicableVariants(experiments, experiment_groups, this.currentConfigData?.dimensions || {}, queryData, identifier);
16775
- if (variantIds.length > 0) {
16776
- queryData.variantIds = variantIds;
16777
- }
16812
+ if (!this.currentConfigData) {
16813
+ const configData = await this.fetchConfigData();
16814
+ this.providerCache.initConfig(configData.default_configs, configData.contexts, configData.overrides, configData.dimensions);
16778
16815
  }
16779
- const result = this.resolver.resolveConfig(configData.default_configs || {}, configData.contexts || [], configData.overrides || {}, configData.dimensions || {}, queryData, "merge");
16780
- return result;
16816
+ if (this.experimentationClient && targetingKey &&
16817
+ !this.experimentationClient.getCachedExperiments()) {
16818
+ await this.experimentationClient.getExperiments();
16819
+ await this.experimentationClient.getExperimentGroups();
16820
+ this.reinitExperimentsCache();
16821
+ }
16822
+ return this.providerCache.evalConfig(context, "merge", undefined, targetingKey);
16781
16823
  }
16782
16824
  catch (error) {
16783
16825
  if (this.defaults) {
@@ -16786,16 +16828,20 @@ class ConfigurationClient {
16786
16828
  throw error;
16787
16829
  }
16788
16830
  }
16789
- // Add method to get applicable variants
16790
- async getApplicableVariants(experiments, experiment_groups, dimensions, queryData, identifier, filterPrefixes) {
16791
- // This would use the native resolver's getApplicableVariants method
16792
- return this.resolver.getApplicableVariants(experiments, experiment_groups, dimensions, queryData, identifier, filterPrefixes || []);
16793
- }
16794
16831
  // Add method to close and cleanup
16795
16832
  async close() {
16833
+ _cacheRegistry.unregister(this);
16834
+ if (this.refreshInterval) {
16835
+ clearTimeout(this.refreshInterval);
16836
+ this.refreshInterval = null;
16837
+ }
16796
16838
  if (this.experimentationClient) {
16797
16839
  await this.experimentationClient.close();
16798
16840
  }
16841
+ if (this.providerCache) {
16842
+ this.providerCache.free();
16843
+ this.providerCache = null;
16844
+ }
16799
16845
  }
16800
16846
  }
16801
16847
 
@@ -16860,6 +16906,11 @@ class NativeResolver {
16860
16906
  this.lib.core_test_connection = this.lib.func("int core_test_connection()");
16861
16907
  this.lib.core_parse_toml_config = this.lib.func("char* core_parse_toml_config(const char*, char*)");
16862
16908
  this.lib.core_parse_json_config = this.lib.func("char* core_parse_json_config(const char*, char*)");
16909
+ this.lib.core_provider_cache_new = this.lib.func("void* core_provider_cache_new()");
16910
+ this.lib.core_provider_cache_free = this.lib.func("void core_provider_cache_free(void*)");
16911
+ this.lib.core_provider_cache_init_config = this.lib.func("void core_provider_cache_init_config(void*, const char*, const char*, const char*, const char*, char*)");
16912
+ this.lib.core_provider_cache_init_experiments = this.lib.func("void core_provider_cache_init_experiments(void*, const char*, const char*, char*)");
16913
+ this.lib.core_provider_cache_eval_config = this.lib.func("char* core_provider_cache_eval_config(void*, const char*, const char*, const char*, const char*, char*)");
16863
16914
  this.isAvailable = true;
16864
16915
  }
16865
16916
  catch (error) {
@@ -17188,6 +17239,51 @@ class NativeResolver {
17188
17239
  return false;
17189
17240
  }
17190
17241
  }
17242
+ createProviderCache() {
17243
+ if (!this.isAvailable) {
17244
+ throw new Error("Native resolver is not available.");
17245
+ }
17246
+ const handle = this.lib.core_provider_cache_new();
17247
+ if (!handle) {
17248
+ throw new Error("core_provider_cache_new returned null");
17249
+ }
17250
+ const lib = this.lib;
17251
+ return {
17252
+ initConfig(defaultConfigs, contexts, overrides, dimensions) {
17253
+ const ebuf = buffer.Buffer.alloc(ERROR_BUFFER_SIZE);
17254
+ lib.core_provider_cache_init_config(handle, JSON.stringify(defaultConfigs || {}), JSON.stringify(contexts || []), JSON.stringify(overrides || {}), JSON.stringify(dimensions || {}), ebuf);
17255
+ const err = ebuf.toString('utf8').split('\0')[0];
17256
+ if (err.length !== 0)
17257
+ throw new Error("ffi: " + err);
17258
+ },
17259
+ initExperiments(experiments, experimentGroups) {
17260
+ const ebuf = buffer.Buffer.alloc(ERROR_BUFFER_SIZE);
17261
+ lib.core_provider_cache_init_experiments(handle, JSON.stringify(experiments || []), JSON.stringify(experimentGroups || []), ebuf);
17262
+ const err = ebuf.toString('utf8').split('\0')[0];
17263
+ if (err.length !== 0)
17264
+ throw new Error("ffi: " + err);
17265
+ },
17266
+ evalConfig(queryData, mergeStrategy = "merge", filterPrefixes, targetingKey) {
17267
+ const ebuf = buffer.Buffer.alloc(ERROR_BUFFER_SIZE);
17268
+ const filterPrefixesJson = filterPrefixes && filterPrefixes.length > 0
17269
+ ? JSON.stringify(filterPrefixes)
17270
+ : null;
17271
+ const result = lib.core_provider_cache_eval_config(handle, JSON.stringify(queryData || {}), mergeStrategy, filterPrefixesJson, targetingKey || null, ebuf);
17272
+ const err = ebuf.toString('utf8').split('\0')[0];
17273
+ if (err.length !== 0)
17274
+ throw new Error("ffi: " + err);
17275
+ const configStr = typeof result === "string"
17276
+ ? result
17277
+ : lib.decode(result, "string");
17278
+ if (typeof result !== "string")
17279
+ lib.core_free_string(result);
17280
+ return JSON.parse(configStr);
17281
+ },
17282
+ free() {
17283
+ lib.core_provider_cache_free(handle);
17284
+ },
17285
+ };
17286
+ }
17191
17287
  throwFFIError(err) {
17192
17288
  throw new Error("ffi: " + err);
17193
17289
  }