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.
@@ -8,15 +8,17 @@ export declare class ConfigurationClient {
8
8
  private currentConfigData;
9
9
  private experimentationClient?;
10
10
  private experimentationOptions?;
11
+ private providerCache;
12
+ private refreshInterval;
11
13
  private defaults;
12
14
  private smithyClient;
13
15
  constructor(config: SuperpositionOptions, resolver: NativeResolver, options?: ConfigOptions, experimentationOptions?: ExperimentationOptions);
14
16
  initialize(): Promise<void>;
17
+ private reinitExperimentsCache;
15
18
  eval(queryData: Record<string, any>, filterPrefixes?: string[], targetingKey?: string): Promise<any>;
16
19
  eval<T>(queryData: Record<string, any>, filterPrefixes?: string[], targetingKey?: string): Promise<T>;
17
20
  setDefault(defaults: ConfigData): void;
18
21
  private fetchConfigData;
19
22
  getAllConfigValue(defaultValue: Record<string, any>, context: Record<string, any>, targetingKey?: string): Promise<Record<string, any>>;
20
- private getApplicableVariants;
21
23
  close(): Promise<void>;
22
24
  }
@@ -35,7 +35,8 @@ export declare class ExperimentationClient {
35
35
  private lastUpdated;
36
36
  private evaluationCache;
37
37
  private pollingInterval?;
38
- constructor(superpositionOptions: SuperpositionOptions, experimentOptions: ExperimentationOptions);
38
+ private onExperimentsChange?;
39
+ constructor(superpositionOptions: SuperpositionOptions, experimentOptions: ExperimentationOptions, onExperimentsChange?: () => void);
39
40
  initialize(): Promise<void>;
40
41
  private startPolling;
41
42
  private fetchExperiments;
@@ -47,6 +48,8 @@ export declare class ExperimentationClient {
47
48
  private normalizeToStringRecord;
48
49
  getExperiments(): Promise<Experiment[]>;
49
50
  getExperimentGroups(): Promise<ExperimentGroup[]>;
51
+ getCachedExperiments(): Experiment[] | null;
52
+ getCachedExperimentGroups(): ExperimentGroup[] | null;
50
53
  generateCacheKey(queryData: Record<string, any>): string;
51
54
  getFromEvalCache(key: string): any | undefined;
52
55
  setEvalCache(key: string, value: any): void;
package/dist/index.esm.js CHANGED
@@ -3180,7 +3180,7 @@ var runtimeConfig = {};
3180
3180
 
3181
3181
  var name = "superposition-sdk";
3182
3182
  var description = "superposition-sdk client";
3183
- var version = "0.104.0";
3183
+ var version = "0.106.0";
3184
3184
  var repository = {
3185
3185
  type: "git",
3186
3186
  url: "git+https://github.com/juspay/superposition.git"
@@ -16389,13 +16389,14 @@ function requireModels () {
16389
16389
  } (superpositionSdk));
16390
16390
 
16391
16391
  class ExperimentationClient {
16392
- constructor(superpositionOptions, experimentOptions) {
16392
+ constructor(superpositionOptions, experimentOptions, onExperimentsChange) {
16393
16393
  this.superpositionOptions = superpositionOptions;
16394
16394
  this.cachedExperiments = null;
16395
16395
  this.cachedExperimentGroups = null;
16396
16396
  this.lastUpdated = null;
16397
16397
  this.evaluationCache = new Map();
16398
16398
  this.options = experimentOptions;
16399
+ this.onExperimentsChange = onExperimentsChange;
16399
16400
  this.smithyClient = new superpositionSdk.SuperpositionClient({
16400
16401
  endpoint: superpositionOptions.endpoint,
16401
16402
  token: { token: superpositionOptions.token },
@@ -16409,6 +16410,7 @@ class ExperimentationClient {
16409
16410
  if (experiments && experimentgroups) {
16410
16411
  this.cachedExperiments = experiments;
16411
16412
  this.cachedExperimentGroups = experimentgroups;
16413
+ this.onExperimentsChange?.();
16412
16414
  this.lastUpdated = new Date();
16413
16415
  console.log("Experiments and Experiment Groups fetched successfully.");
16414
16416
  }
@@ -16420,21 +16422,28 @@ class ExperimentationClient {
16420
16422
  }
16421
16423
  }
16422
16424
  startPolling(interval) {
16423
- this.pollingInterval = setInterval(async () => {
16425
+ const weakSelf = new WeakRef(this);
16426
+ const poll = async () => {
16427
+ const self = weakSelf.deref();
16428
+ if (!self)
16429
+ return;
16424
16430
  try {
16425
- const experiments = await this.fetchExperiments();
16426
- const experimentGroups = await this.fetchExperimentGroups();
16431
+ const experiments = await self.fetchExperiments();
16432
+ const experimentGroups = await self.fetchExperimentGroups();
16427
16433
  if (experiments && experimentGroups) {
16428
- this.cachedExperiments = experiments;
16429
- this.cachedExperimentGroups = experimentGroups;
16430
- this.lastUpdated = new Date();
16434
+ self.cachedExperiments = experiments;
16435
+ self.cachedExperimentGroups = experimentGroups;
16436
+ self.lastUpdated = new Date();
16431
16437
  console.log("Experiments and Experiment Groups refreshed successfully.");
16438
+ self.onExperimentsChange?.();
16432
16439
  }
16433
16440
  }
16434
16441
  catch (error) {
16435
16442
  console.error("Polling error:", error);
16436
16443
  }
16437
- }, interval);
16444
+ self.pollingInterval = setTimeout(poll, interval);
16445
+ };
16446
+ this.pollingInterval = setTimeout(poll, interval);
16438
16447
  }
16439
16448
  async fetchExperiments() {
16440
16449
  try {
@@ -16487,7 +16496,7 @@ class ExperimentationClient {
16487
16496
  id: exp.id,
16488
16497
  context: this.normalizeToStringRecord(exp.context),
16489
16498
  variants: variants,
16490
- traffic_percentage: exp.traffic_percentage || 100,
16499
+ traffic_percentage: exp.traffic_percentage ?? 100,
16491
16500
  status: exp.status || superpositionSdk.ExperimentStatusType.DISCARDED,
16492
16501
  });
16493
16502
  }
@@ -16521,7 +16530,7 @@ class ExperimentationClient {
16521
16530
  experimentGroups.push({
16522
16531
  id: exp_group.id,
16523
16532
  context: this.normalizeToStringRecord(exp_group.context),
16524
- traffic_percentage: exp_group.traffic_percentage || 100,
16533
+ traffic_percentage: exp_group.traffic_percentage ?? 100,
16525
16534
  member_experiment_ids: exp_group.member_experiment_ids || [],
16526
16535
  group_type: exp_group.group_type ||
16527
16536
  superpositionSdk.GroupType.USER_CREATED,
@@ -16580,6 +16589,7 @@ class ExperimentationClient {
16580
16589
  if (experiments) {
16581
16590
  this.cachedExperiments = experiments;
16582
16591
  this.lastUpdated = new Date();
16592
+ this.onExperimentsChange?.();
16583
16593
  }
16584
16594
  }
16585
16595
  catch (error) {
@@ -16597,6 +16607,7 @@ class ExperimentationClient {
16597
16607
  const experiments = await this.fetchExperiments();
16598
16608
  if (experiments) {
16599
16609
  this.cachedExperiments = experiments;
16610
+ this.onExperimentsChange?.();
16600
16611
  this.lastUpdated = new Date();
16601
16612
  }
16602
16613
  }
@@ -16617,6 +16628,7 @@ class ExperimentationClient {
16617
16628
  if (experimentGroups) {
16618
16629
  this.cachedExperimentGroups = experimentGroups;
16619
16630
  this.lastUpdated = new Date();
16631
+ this.onExperimentsChange?.();
16620
16632
  }
16621
16633
  }
16622
16634
  catch (error) {
@@ -16634,11 +16646,18 @@ class ExperimentationClient {
16634
16646
  const experimentGroups = await this.fetchExperimentGroups();
16635
16647
  if (experimentGroups) {
16636
16648
  this.cachedExperimentGroups = experimentGroups;
16649
+ this.onExperimentsChange?.();
16637
16650
  this.lastUpdated = new Date();
16638
16651
  }
16639
16652
  }
16640
16653
  return this.cachedExperimentGroups || [];
16641
16654
  }
16655
+ getCachedExperiments() {
16656
+ return this.cachedExperiments;
16657
+ }
16658
+ getCachedExperimentGroups() {
16659
+ return this.cachedExperimentGroups;
16660
+ }
16642
16661
  generateCacheKey(queryData) {
16643
16662
  return JSON.stringify(queryData, Object.keys(queryData).sort());
16644
16663
  }
@@ -16654,11 +16673,12 @@ class ExperimentationClient {
16654
16673
  async close() {
16655
16674
  try {
16656
16675
  if (this.pollingInterval) {
16657
- clearInterval(this.pollingInterval);
16676
+ clearTimeout(this.pollingInterval);
16658
16677
  console.log("Polling stopped successfully");
16659
16678
  }
16660
16679
  this.clearEvalCache();
16661
16680
  this.cachedExperiments = null;
16681
+ this.cachedExperimentGroups = null;
16662
16682
  this.lastUpdated = null;
16663
16683
  console.log("ExperimentationClient closed successfully");
16664
16684
  }
@@ -16669,30 +16689,47 @@ class ExperimentationClient {
16669
16689
  }
16670
16690
  }
16671
16691
 
16692
+ const _cacheRegistry = new FinalizationRegistry((freeFn) => {
16693
+ setImmediate(freeFn);
16694
+ });
16672
16695
  class ConfigurationClient {
16673
16696
  constructor(config, resolver, options = {}, experimentationOptions) {
16674
16697
  this.currentConfigData = null;
16698
+ this.providerCache = null;
16699
+ this.refreshInterval = null;
16675
16700
  this.defaults = null;
16676
16701
  this.config = config;
16677
16702
  this.resolver = resolver;
16678
16703
  this.options = options;
16704
+ this.providerCache = resolver.createProviderCache();
16705
+ const cache = this.providerCache;
16706
+ _cacheRegistry.register(this, () => {
16707
+ cache.free();
16708
+ }, this);
16679
16709
  if (this.options.refreshStrategy &&
16680
16710
  "interval" in this.options.refreshStrategy) {
16681
16711
  const strategy = this.options.refreshStrategy;
16682
- setInterval(async () => {
16712
+ const weakSelf = new WeakRef(this);
16713
+ const poll = async () => {
16714
+ const self = weakSelf.deref();
16715
+ if (!self)
16716
+ return;
16683
16717
  try {
16684
- const refreshedConfig = await this.fetchConfigData();
16685
- this.currentConfigData = refreshedConfig;
16718
+ const refreshedConfig = await self.fetchConfigData();
16719
+ self.currentConfigData = refreshedConfig;
16720
+ self.providerCache?.initConfig(refreshedConfig.default_configs, refreshedConfig.contexts, refreshedConfig.overrides, refreshedConfig.dimensions);
16686
16721
  console.log("Configuration refreshed successfully.");
16687
16722
  }
16688
16723
  catch (error) {
16689
16724
  console.error("Failed to refresh configuration. Will continue to use the last known good configuration.", error);
16690
16725
  }
16691
- }, strategy.interval);
16726
+ self.refreshInterval = setTimeout(poll, strategy.interval);
16727
+ };
16728
+ this.refreshInterval = setTimeout(poll, strategy.interval);
16692
16729
  }
16693
16730
  if (experimentationOptions) {
16694
16731
  this.experimentationOptions = experimentationOptions;
16695
- this.experimentationClient = new ExperimentationClient(config, experimentationOptions);
16732
+ this.experimentationClient = new ExperimentationClient(config, experimentationOptions, this.reinitExperimentsCache.bind(this));
16696
16733
  }
16697
16734
  this.smithyClient = new superpositionSdk.SuperpositionClient({
16698
16735
  endpoint: this.config.endpoint,
@@ -16701,26 +16738,35 @@ class ConfigurationClient {
16701
16738
  });
16702
16739
  }
16703
16740
  async initialize() {
16704
- // Initialize experimentation client if present
16741
+ const configData = await this.fetchConfigData();
16742
+ this.providerCache.initConfig(configData.default_configs, configData.contexts, configData.overrides, configData.dimensions);
16705
16743
  if (this.experimentationClient) {
16706
16744
  await this.experimentationClient.initialize();
16745
+ this.reinitExperimentsCache();
16746
+ }
16747
+ }
16748
+ reinitExperimentsCache() {
16749
+ if (!this.experimentationClient || !this.providerCache)
16750
+ return;
16751
+ const experiments = this.experimentationClient.getCachedExperiments();
16752
+ const experimentGroups = this.experimentationClient.getCachedExperimentGroups();
16753
+ if (experiments && experimentGroups) {
16754
+ this.providerCache.initExperiments(experiments, experimentGroups);
16707
16755
  }
16708
16756
  }
16709
16757
  async eval(queryData, filterPrefixes, targetingKey) {
16710
16758
  try {
16711
- const configData = this.currentConfigData || await this.fetchConfigData();
16712
- let experimentationArgs;
16713
- if (this.experimentationClient && targetingKey) {
16714
- const experiments = await this.experimentationClient.getExperiments();
16715
- const experiment_groups = await this.experimentationClient.getExperimentGroups();
16716
- experimentationArgs = {
16717
- experiments,
16718
- experiment_groups,
16719
- targeting_key: targetingKey,
16720
- };
16759
+ if (!this.currentConfigData) {
16760
+ const configData = await this.fetchConfigData();
16761
+ this.providerCache.initConfig(configData.default_configs, configData.contexts, configData.overrides, configData.dimensions);
16721
16762
  }
16722
- const result = this.resolver.resolveConfig(configData.default_configs || {}, configData.contexts || [], configData.overrides || {}, configData.dimensions || {}, queryData, "merge", filterPrefixes, experimentationArgs);
16723
- return result;
16763
+ if (this.experimentationClient && targetingKey &&
16764
+ !this.experimentationClient.getCachedExperiments()) {
16765
+ await this.experimentationClient.getExperiments();
16766
+ await this.experimentationClient.getExperimentGroups();
16767
+ this.reinitExperimentsCache();
16768
+ }
16769
+ return this.providerCache.evalConfig(queryData, "merge", filterPrefixes, targetingKey);
16724
16770
  }
16725
16771
  catch (error) {
16726
16772
  if (this.defaults) {
@@ -16760,21 +16806,17 @@ class ConfigurationClient {
16760
16806
  // TODO: Remove this function all together and use eval for getAllConfigValue as well
16761
16807
  async getAllConfigValue(defaultValue, context, targetingKey) {
16762
16808
  try {
16763
- const configData = this.currentConfigData || await this.fetchConfigData();
16764
- // Prepare query data with experiment variants if applicable
16765
- let queryData = { ...context };
16766
- if (this.experimentationClient && targetingKey) {
16767
- const experiments = await this.experimentationClient.getExperiments();
16768
- const experiment_groups = await this.experimentationClient.getExperimentGroups();
16769
- const identifier = this.experimentationOptions?.defaultIdentifier ||
16770
- (targetingKey ? targetingKey : "default");
16771
- const variantIds = await this.getApplicableVariants(experiments, experiment_groups, this.currentConfigData?.dimensions || {}, queryData, identifier);
16772
- if (variantIds.length > 0) {
16773
- queryData.variantIds = variantIds;
16774
- }
16809
+ if (!this.currentConfigData) {
16810
+ const configData = await this.fetchConfigData();
16811
+ this.providerCache.initConfig(configData.default_configs, configData.contexts, configData.overrides, configData.dimensions);
16775
16812
  }
16776
- const result = this.resolver.resolveConfig(configData.default_configs || {}, configData.contexts || [], configData.overrides || {}, configData.dimensions || {}, queryData, "merge");
16777
- return result;
16813
+ if (this.experimentationClient && targetingKey &&
16814
+ !this.experimentationClient.getCachedExperiments()) {
16815
+ await this.experimentationClient.getExperiments();
16816
+ await this.experimentationClient.getExperimentGroups();
16817
+ this.reinitExperimentsCache();
16818
+ }
16819
+ return this.providerCache.evalConfig(context, "merge", undefined, targetingKey);
16778
16820
  }
16779
16821
  catch (error) {
16780
16822
  if (this.defaults) {
@@ -16783,16 +16825,20 @@ class ConfigurationClient {
16783
16825
  throw error;
16784
16826
  }
16785
16827
  }
16786
- // Add method to get applicable variants
16787
- async getApplicableVariants(experiments, experiment_groups, dimensions, queryData, identifier, filterPrefixes) {
16788
- // This would use the native resolver's getApplicableVariants method
16789
- return this.resolver.getApplicableVariants(experiments, experiment_groups, dimensions, queryData, identifier, filterPrefixes || []);
16790
- }
16791
16828
  // Add method to close and cleanup
16792
16829
  async close() {
16830
+ _cacheRegistry.unregister(this);
16831
+ if (this.refreshInterval) {
16832
+ clearTimeout(this.refreshInterval);
16833
+ this.refreshInterval = null;
16834
+ }
16793
16835
  if (this.experimentationClient) {
16794
16836
  await this.experimentationClient.close();
16795
16837
  }
16838
+ if (this.providerCache) {
16839
+ this.providerCache.free();
16840
+ this.providerCache = null;
16841
+ }
16796
16842
  }
16797
16843
  }
16798
16844
 
@@ -16857,6 +16903,11 @@ class NativeResolver {
16857
16903
  this.lib.core_test_connection = this.lib.func("int core_test_connection()");
16858
16904
  this.lib.core_parse_toml_config = this.lib.func("char* core_parse_toml_config(const char*, char*)");
16859
16905
  this.lib.core_parse_json_config = this.lib.func("char* core_parse_json_config(const char*, char*)");
16906
+ this.lib.core_provider_cache_new = this.lib.func("void* core_provider_cache_new()");
16907
+ this.lib.core_provider_cache_free = this.lib.func("void core_provider_cache_free(void*)");
16908
+ 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*)");
16909
+ this.lib.core_provider_cache_init_experiments = this.lib.func("void core_provider_cache_init_experiments(void*, const char*, const char*, char*)");
16910
+ 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*)");
16860
16911
  this.isAvailable = true;
16861
16912
  }
16862
16913
  catch (error) {
@@ -17185,6 +17236,51 @@ class NativeResolver {
17185
17236
  return false;
17186
17237
  }
17187
17238
  }
17239
+ createProviderCache() {
17240
+ if (!this.isAvailable) {
17241
+ throw new Error("Native resolver is not available.");
17242
+ }
17243
+ const handle = this.lib.core_provider_cache_new();
17244
+ if (!handle) {
17245
+ throw new Error("core_provider_cache_new returned null");
17246
+ }
17247
+ const lib = this.lib;
17248
+ return {
17249
+ initConfig(defaultConfigs, contexts, overrides, dimensions) {
17250
+ const ebuf = Buffer$1.alloc(ERROR_BUFFER_SIZE);
17251
+ lib.core_provider_cache_init_config(handle, JSON.stringify(defaultConfigs || {}), JSON.stringify(contexts || []), JSON.stringify(overrides || {}), JSON.stringify(dimensions || {}), ebuf);
17252
+ const err = ebuf.toString('utf8').split('\0')[0];
17253
+ if (err.length !== 0)
17254
+ throw new Error("ffi: " + err);
17255
+ },
17256
+ initExperiments(experiments, experimentGroups) {
17257
+ const ebuf = Buffer$1.alloc(ERROR_BUFFER_SIZE);
17258
+ lib.core_provider_cache_init_experiments(handle, JSON.stringify(experiments || []), JSON.stringify(experimentGroups || []), ebuf);
17259
+ const err = ebuf.toString('utf8').split('\0')[0];
17260
+ if (err.length !== 0)
17261
+ throw new Error("ffi: " + err);
17262
+ },
17263
+ evalConfig(queryData, mergeStrategy = "merge", filterPrefixes, targetingKey) {
17264
+ const ebuf = Buffer$1.alloc(ERROR_BUFFER_SIZE);
17265
+ const filterPrefixesJson = filterPrefixes && filterPrefixes.length > 0
17266
+ ? JSON.stringify(filterPrefixes)
17267
+ : null;
17268
+ const result = lib.core_provider_cache_eval_config(handle, JSON.stringify(queryData || {}), mergeStrategy, filterPrefixesJson, targetingKey || null, ebuf);
17269
+ const err = ebuf.toString('utf8').split('\0')[0];
17270
+ if (err.length !== 0)
17271
+ throw new Error("ffi: " + err);
17272
+ const configStr = typeof result === "string"
17273
+ ? result
17274
+ : lib.decode(result, "string");
17275
+ if (typeof result !== "string")
17276
+ lib.core_free_string(result);
17277
+ return JSON.parse(configStr);
17278
+ },
17279
+ free() {
17280
+ lib.core_provider_cache_free(handle);
17281
+ },
17282
+ };
17283
+ }
17188
17284
  throwFFIError(err) {
17189
17285
  throw new Error("ffi: " + err);
17190
17286
  }