superposition-provider 0.103.0 → 0.105.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,10 +8,13 @@ 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;
@@ -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: (experiments: Experiment[], experimentGroups: ExperimentGroup[]) => 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.103.0";
3183
+ var version = "0.105.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(experiments, experimentgroups);
16412
16414
  this.lastUpdated = new Date();
16413
16415
  console.log("Experiments and Experiment Groups fetched successfully.");
16414
16416
  }
@@ -16420,21 +16422,31 @@ 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
+ let 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(experiments, experimentGroups);
16432
16439
  }
16433
16440
  }
16434
16441
  catch (error) {
16435
16442
  console.error("Polling error:", error);
16436
16443
  }
16437
- }, interval);
16444
+ if (self) {
16445
+ self.pollingInterval = setTimeout(poll, interval);
16446
+ }
16447
+ self = undefined;
16448
+ };
16449
+ this.pollingInterval = setTimeout(poll, interval);
16438
16450
  }
16439
16451
  async fetchExperiments() {
16440
16452
  try {
@@ -16487,7 +16499,7 @@ class ExperimentationClient {
16487
16499
  id: exp.id,
16488
16500
  context: this.normalizeToStringRecord(exp.context),
16489
16501
  variants: variants,
16490
- traffic_percentage: exp.traffic_percentage || 100,
16502
+ traffic_percentage: exp.traffic_percentage ?? 100,
16491
16503
  status: exp.status || superpositionSdk.ExperimentStatusType.DISCARDED,
16492
16504
  });
16493
16505
  }
@@ -16521,7 +16533,7 @@ class ExperimentationClient {
16521
16533
  experimentGroups.push({
16522
16534
  id: exp_group.id,
16523
16535
  context: this.normalizeToStringRecord(exp_group.context),
16524
- traffic_percentage: exp_group.traffic_percentage || 100,
16536
+ traffic_percentage: exp_group.traffic_percentage ?? 100,
16525
16537
  member_experiment_ids: exp_group.member_experiment_ids || [],
16526
16538
  group_type: exp_group.group_type ||
16527
16539
  superpositionSdk.GroupType.USER_CREATED,
@@ -16580,6 +16592,7 @@ class ExperimentationClient {
16580
16592
  if (experiments) {
16581
16593
  this.cachedExperiments = experiments;
16582
16594
  this.lastUpdated = new Date();
16595
+ this.onExperimentsChange(experiments, this.cachedExperimentGroups || []);
16583
16596
  }
16584
16597
  }
16585
16598
  catch (error) {
@@ -16597,6 +16610,7 @@ class ExperimentationClient {
16597
16610
  const experiments = await this.fetchExperiments();
16598
16611
  if (experiments) {
16599
16612
  this.cachedExperiments = experiments;
16613
+ this.onExperimentsChange(experiments, this.cachedExperimentGroups || []);
16600
16614
  this.lastUpdated = new Date();
16601
16615
  }
16602
16616
  }
@@ -16617,6 +16631,7 @@ class ExperimentationClient {
16617
16631
  if (experimentGroups) {
16618
16632
  this.cachedExperimentGroups = experimentGroups;
16619
16633
  this.lastUpdated = new Date();
16634
+ this.onExperimentsChange(this.cachedExperiments || [], experimentGroups);
16620
16635
  }
16621
16636
  }
16622
16637
  catch (error) {
@@ -16634,11 +16649,18 @@ class ExperimentationClient {
16634
16649
  const experimentGroups = await this.fetchExperimentGroups();
16635
16650
  if (experimentGroups) {
16636
16651
  this.cachedExperimentGroups = experimentGroups;
16652
+ this.onExperimentsChange(this.cachedExperiments || [], experimentGroups);
16637
16653
  this.lastUpdated = new Date();
16638
16654
  }
16639
16655
  }
16640
16656
  return this.cachedExperimentGroups || [];
16641
16657
  }
16658
+ getCachedExperiments() {
16659
+ return this.cachedExperiments;
16660
+ }
16661
+ getCachedExperimentGroups() {
16662
+ return this.cachedExperimentGroups;
16663
+ }
16642
16664
  generateCacheKey(queryData) {
16643
16665
  return JSON.stringify(queryData, Object.keys(queryData).sort());
16644
16666
  }
@@ -16654,7 +16676,7 @@ class ExperimentationClient {
16654
16676
  async close() {
16655
16677
  try {
16656
16678
  if (this.pollingInterval) {
16657
- clearInterval(this.pollingInterval);
16679
+ clearTimeout(this.pollingInterval);
16658
16680
  console.log("Polling stopped successfully");
16659
16681
  }
16660
16682
  this.clearEvalCache();
@@ -16669,30 +16691,50 @@ class ExperimentationClient {
16669
16691
  }
16670
16692
  }
16671
16693
 
16694
+ const _cacheRegistry = new FinalizationRegistry((freeFn) => {
16695
+ setImmediate(freeFn);
16696
+ });
16672
16697
  class ConfigurationClient {
16673
16698
  constructor(config, resolver, options = {}, experimentationOptions) {
16674
16699
  this.currentConfigData = null;
16700
+ this.providerCache = null;
16701
+ this.refreshInterval = null;
16675
16702
  this.defaults = null;
16676
16703
  this.config = config;
16677
16704
  this.resolver = resolver;
16678
16705
  this.options = options;
16706
+ this.providerCache = resolver.createProviderCache();
16707
+ const cache = this.providerCache;
16708
+ _cacheRegistry.register(this, () => {
16709
+ cache.free();
16710
+ }, this);
16679
16711
  if (this.options.refreshStrategy &&
16680
16712
  "interval" in this.options.refreshStrategy) {
16681
16713
  const strategy = this.options.refreshStrategy;
16682
- setInterval(async () => {
16714
+ const weakSelf = new WeakRef(this);
16715
+ const poll = async () => {
16716
+ let self = weakSelf.deref();
16717
+ if (!self)
16718
+ return;
16683
16719
  try {
16684
- const refreshedConfig = await this.fetchConfigData();
16685
- this.currentConfigData = refreshedConfig;
16720
+ const refreshedConfig = await self.fetchConfigData();
16721
+ self.currentConfigData = refreshedConfig;
16722
+ self.providerCache?.initConfig(refreshedConfig.default_configs, refreshedConfig.contexts, refreshedConfig.overrides, refreshedConfig.dimensions);
16686
16723
  console.log("Configuration refreshed successfully.");
16687
16724
  }
16688
16725
  catch (error) {
16689
16726
  console.error("Failed to refresh configuration. Will continue to use the last known good configuration.", error);
16690
16727
  }
16691
- }, strategy.interval);
16728
+ if (self) {
16729
+ self.refreshInterval = setTimeout(poll, strategy.interval);
16730
+ }
16731
+ self = undefined;
16732
+ };
16733
+ this.refreshInterval = setTimeout(poll, strategy.interval);
16692
16734
  }
16693
16735
  if (experimentationOptions) {
16694
16736
  this.experimentationOptions = experimentationOptions;
16695
- this.experimentationClient = new ExperimentationClient(config, experimentationOptions);
16737
+ this.experimentationClient = new ExperimentationClient(config, experimentationOptions, this.reinitExperimentsCache.bind(this));
16696
16738
  }
16697
16739
  this.smithyClient = new superpositionSdk.SuperpositionClient({
16698
16740
  endpoint: this.config.endpoint,
@@ -16701,26 +16743,30 @@ class ConfigurationClient {
16701
16743
  });
16702
16744
  }
16703
16745
  async initialize() {
16704
- // Initialize experimentation client if present
16746
+ const configData = await this.fetchConfigData();
16747
+ this.providerCache.initConfig(configData.default_configs, configData.contexts, configData.overrides, configData.dimensions);
16705
16748
  if (this.experimentationClient) {
16706
16749
  await this.experimentationClient.initialize();
16707
16750
  }
16708
16751
  }
16752
+ reinitExperimentsCache(experiments, experimentGroups) {
16753
+ if (experiments && experimentGroups) {
16754
+ this.providerCache.initExperiments(experiments, experimentGroups);
16755
+ }
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 &&
16764
+ targetingKey &&
16765
+ !this.experimentationClient.getCachedExperiments()) {
16766
+ await this.experimentationClient.getExperiments();
16767
+ await this.experimentationClient.getExperimentGroups();
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 &&
16814
+ targetingKey &&
16815
+ !this.experimentationClient.getCachedExperiments()) {
16816
+ await this.experimentationClient.getExperiments();
16817
+ await this.experimentationClient.getExperimentGroups();
16818
+ }
16819
+ return this.providerCache.evalConfig(context, "merge", undefined, targetingKey);
16778
16820
  }
16779
16821
  catch (error) {
16780
16822
  if (this.defaults) {
@@ -16790,9 +16832,18 @@ class ConfigurationClient {
16790
16832
  }
16791
16833
  // Add method to close and cleanup
16792
16834
  async close() {
16835
+ _cacheRegistry.unregister(this);
16836
+ if (this.refreshInterval) {
16837
+ clearTimeout(this.refreshInterval);
16838
+ this.refreshInterval = null;
16839
+ }
16793
16840
  if (this.experimentationClient) {
16794
16841
  await this.experimentationClient.close();
16795
16842
  }
16843
+ if (this.providerCache) {
16844
+ this.providerCache.free();
16845
+ this.providerCache = null;
16846
+ }
16796
16847
  }
16797
16848
  }
16798
16849
 
@@ -16857,6 +16908,11 @@ class NativeResolver {
16857
16908
  this.lib.core_test_connection = this.lib.func("int core_test_connection()");
16858
16909
  this.lib.core_parse_toml_config = this.lib.func("char* core_parse_toml_config(const char*, char*)");
16859
16910
  this.lib.core_parse_json_config = this.lib.func("char* core_parse_json_config(const char*, char*)");
16911
+ this.lib.core_provider_cache_new = this.lib.func("void* core_provider_cache_new()");
16912
+ this.lib.core_provider_cache_free = this.lib.func("void core_provider_cache_free(void*)");
16913
+ 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*)");
16914
+ this.lib.core_provider_cache_init_experiments = this.lib.func("void core_provider_cache_init_experiments(void*, const char*, const char*, char*)");
16915
+ 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
16916
  this.isAvailable = true;
16861
16917
  }
16862
16918
  catch (error) {
@@ -17185,6 +17241,51 @@ class NativeResolver {
17185
17241
  return false;
17186
17242
  }
17187
17243
  }
17244
+ createProviderCache() {
17245
+ if (!this.isAvailable) {
17246
+ throw new Error("Native resolver is not available.");
17247
+ }
17248
+ const handle = this.lib.core_provider_cache_new();
17249
+ if (!handle) {
17250
+ throw new Error("core_provider_cache_new returned null");
17251
+ }
17252
+ const lib = this.lib;
17253
+ return {
17254
+ initConfig(defaultConfigs, contexts, overrides, dimensions) {
17255
+ const ebuf = Buffer$1.alloc(ERROR_BUFFER_SIZE);
17256
+ lib.core_provider_cache_init_config(handle, JSON.stringify(defaultConfigs || {}), JSON.stringify(contexts || []), JSON.stringify(overrides || {}), JSON.stringify(dimensions || {}), ebuf);
17257
+ const err = ebuf.toString('utf8').split('\0')[0];
17258
+ if (err.length !== 0)
17259
+ throw new Error("ffi: " + err);
17260
+ },
17261
+ initExperiments(experiments, experimentGroups) {
17262
+ const ebuf = Buffer$1.alloc(ERROR_BUFFER_SIZE);
17263
+ lib.core_provider_cache_init_experiments(handle, JSON.stringify(experiments || []), JSON.stringify(experimentGroups || []), ebuf);
17264
+ const err = ebuf.toString('utf8').split('\0')[0];
17265
+ if (err.length !== 0)
17266
+ throw new Error("ffi: " + err);
17267
+ },
17268
+ evalConfig(queryData, mergeStrategy = "merge", filterPrefixes, targetingKey) {
17269
+ const ebuf = Buffer$1.alloc(ERROR_BUFFER_SIZE);
17270
+ const filterPrefixesJson = filterPrefixes && filterPrefixes.length > 0
17271
+ ? JSON.stringify(filterPrefixes)
17272
+ : null;
17273
+ const result = lib.core_provider_cache_eval_config(handle, JSON.stringify(queryData || {}), mergeStrategy, filterPrefixesJson, targetingKey || null, ebuf);
17274
+ const err = ebuf.toString('utf8').split('\0')[0];
17275
+ if (err.length !== 0)
17276
+ throw new Error("ffi: " + err);
17277
+ const configStr = typeof result === "string"
17278
+ ? result
17279
+ : lib.decode(result, "string");
17280
+ if (typeof result !== "string")
17281
+ lib.core_free_string(result);
17282
+ return JSON.parse(configStr);
17283
+ },
17284
+ free() {
17285
+ lib.core_provider_cache_free(handle);
17286
+ },
17287
+ };
17288
+ }
17188
17289
  throwFFIError(err) {
17189
17290
  throw new Error("ffi: " + err);
17190
17291
  }