wolves-js-client 1.0.4 → 1.0.5

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/Store.d.ts CHANGED
@@ -9,7 +9,16 @@ export type InitializeResponse = {
9
9
  has_updates: boolean;
10
10
  time: number;
11
11
  };
12
- export type DataSource = 'Network' | 'Cache' | 'NetworkNotModified' | 'NoValues';
12
+ export type DataSource = 'Network' | 'Cache' | 'NetworkNotModified' | 'NoValues' | 'Uninitialized';
13
+ export type EvaluationDetails = {
14
+ source: DataSource;
15
+ reason: string;
16
+ isRecognized: boolean;
17
+ };
18
+ export type ExperimentEvaluation = {
19
+ config: ExperimentConfig | null;
20
+ details: EvaluationDetails;
21
+ };
13
22
  export type DataAdapterResult = {
14
23
  source: DataSource;
15
24
  data: string;
@@ -23,6 +32,7 @@ export type DataAdapterResult = {
23
32
  export declare class Store {
24
33
  private values;
25
34
  private source;
35
+ private isInitialized;
26
36
  private sdkKey;
27
37
  private cacheLimit;
28
38
  private inMemoryCache;
@@ -60,7 +70,23 @@ export declare class Store {
60
70
  * Reset the store for a new user.
61
71
  */
62
72
  reset(): void;
63
- getExperiment(name: string): ExperimentConfig | null;
73
+ /**
74
+ * Mark the store as initialized.
75
+ */
76
+ finalize(): void;
77
+ /**
78
+ * Get experiment with evaluation details including reason.
79
+ * Following Statsig SDK pattern for exposure reasons:
80
+ * - Uninitialized: API called before initialize
81
+ * - NoValues: No data available
82
+ * - Network:Recognized / Network:Unrecognized
83
+ * - Cache:Recognized / Cache:Unrecognized
84
+ */
85
+ getExperiment(name: string): ExperimentEvaluation;
86
+ /**
87
+ * Get the base source for building reason string.
88
+ */
89
+ private _getBaseSource;
64
90
  getValues(): InitializeResponse | null;
65
91
  getSource(): DataSource;
66
92
  getLastUpdateTime(): number | undefined;
package/dist/Store.js CHANGED
@@ -8,13 +8,13 @@ const CACHE_LIMIT = 10;
8
8
  const MAX_CACHE_WRITE_ATTEMPTS = 8;
9
9
  const LAST_MODIFIED_KEY = 'wolves.last_modified_time';
10
10
  /**
11
- * Generate a hash for user identity to create unique cache keys.
11
+ * Generate a user identifier for cache keys.
12
+ * Only uses userID as the unique identifier.
12
13
  */
13
- function _getUserHash(user) {
14
- if (!user)
14
+ function _getUserIdentifier(user) {
15
+ if (!user || !user.userID)
15
16
  return 'anonymous';
16
- const parts = [user.userID || '', user.email || ''];
17
- return parts.join('|');
17
+ return user.userID;
18
18
  }
19
19
  /**
20
20
  * Store class that manages experiment configurations with caching support.
@@ -24,6 +24,7 @@ class Store {
24
24
  constructor(sdkKey = '') {
25
25
  this.values = null;
26
26
  this.source = 'NoValues';
27
+ this.isInitialized = false;
27
28
  this.cacheLimit = CACHE_LIMIT;
28
29
  this.inMemoryCache = new Map();
29
30
  this.sdkKey = sdkKey;
@@ -32,7 +33,7 @@ class Store {
32
33
  * Get the cache key for a specific user.
33
34
  */
34
35
  getCacheKey(user) {
35
- const userHash = _getUserHash(user);
36
+ const userHash = _getUserIdentifier(user);
36
37
  return `${CACHE_KEY_PREFIX}.${this.sdkKey}.${userHash}`;
37
38
  }
38
39
  /**
@@ -109,7 +110,7 @@ class Store {
109
110
  source: 'Network',
110
111
  data: JSON.stringify(values),
111
112
  receivedAt: Date.now(),
112
- fullUserHash: _getUserHash(user),
113
+ fullUserHash: _getUserIdentifier(user),
113
114
  };
114
115
  const cacheKey = this.getCacheKey(user);
115
116
  this.inMemoryCache.set(cacheKey, result);
@@ -164,12 +165,67 @@ class Store {
164
165
  reset() {
165
166
  this.values = null;
166
167
  this.source = 'NoValues';
168
+ this.isInitialized = false;
167
169
  }
170
+ /**
171
+ * Mark the store as initialized.
172
+ */
173
+ finalize() {
174
+ this.isInitialized = true;
175
+ }
176
+ /**
177
+ * Get experiment with evaluation details including reason.
178
+ * Following Statsig SDK pattern for exposure reasons:
179
+ * - Uninitialized: API called before initialize
180
+ * - NoValues: No data available
181
+ * - Network:Recognized / Network:Unrecognized
182
+ * - Cache:Recognized / Cache:Unrecognized
183
+ */
168
184
  getExperiment(name) {
185
+ // Check if uninitialized
186
+ if (!this.isInitialized) {
187
+ return {
188
+ config: null,
189
+ details: {
190
+ source: 'Uninitialized',
191
+ reason: 'Uninitialized',
192
+ isRecognized: false,
193
+ },
194
+ };
195
+ }
196
+ // Check if no values
169
197
  if (!this.values || !this.values.dynamic_configs) {
170
- return null;
198
+ return {
199
+ config: null,
200
+ details: {
201
+ source: this.source,
202
+ reason: 'NoValues',
203
+ isRecognized: false,
204
+ },
205
+ };
171
206
  }
172
- return this.values.dynamic_configs[name] || null;
207
+ const config = this.values.dynamic_configs[name] || null;
208
+ const isRecognized = config !== null;
209
+ const baseSource = this._getBaseSource();
210
+ const subreason = isRecognized ? 'Recognized' : 'Unrecognized';
211
+ const reason = `${baseSource}:${subreason}`;
212
+ return {
213
+ config,
214
+ details: {
215
+ source: this.source,
216
+ reason,
217
+ isRecognized,
218
+ },
219
+ };
220
+ }
221
+ /**
222
+ * Get the base source for building reason string.
223
+ */
224
+ _getBaseSource() {
225
+ if (this.source === 'NetworkNotModified') {
226
+ return 'Network';
227
+ }
228
+ return this.source;
173
229
  }
174
230
  getValues() {
175
231
  return this.values;
@@ -1,5 +1,5 @@
1
1
  import { WolvesUser } from './WolvesUser';
2
- import { Store, DataSource } from './Store';
2
+ import { DataSource } from './Store';
3
3
  import { Experiment } from './Types';
4
4
  export type LoadingStatus = 'Uninitialized' | 'Loading' | 'Ready';
5
5
  export type WolvesUpdateDetails = {
@@ -107,8 +107,4 @@ export declare class WolvesClient {
107
107
  logEvent(eventName: string, value?: string | number, metadata?: Record<string, string>): void;
108
108
  shutdown(): Promise<void>;
109
109
  private logExposure;
110
- /**
111
- * Get the underlying store (for testing purposes).
112
- */
113
- getStore(): Store;
114
110
  }
@@ -147,6 +147,7 @@ class WolvesClient {
147
147
  }
148
148
  // Timeout case - return with cached values
149
149
  this.loadingStatus = 'Ready';
150
+ this.store.finalize();
150
151
  return createUpdateDetails(cachedResult != null, this.store.getSource(), performance.now() - startTime, cachedResult ? null : new Error('Timeout waiting for network'), warnings);
151
152
  });
152
153
  }
@@ -172,14 +173,17 @@ class WolvesClient {
172
173
  this.store.setValuesFromResult(notModifiedResult, user);
173
174
  }
174
175
  this.loadingStatus = 'Ready';
176
+ this.store.finalize();
175
177
  return createUpdateDetails(true, this.store.getSource(), performance.now() - startTime, null, warnings);
176
178
  }
177
179
  // Network fetch failed
178
180
  this.loadingStatus = 'Ready';
181
+ this.store.finalize();
179
182
  return createUpdateDetails(cachedResult != null, this.store.getSource(), performance.now() - startTime, cachedResult ? null : new Error('Failed to fetch config from network'), [...warnings, 'NetworkFetchFailed']);
180
183
  }
181
184
  catch (e) {
182
185
  this.loadingStatus = 'Ready';
186
+ this.store.finalize();
183
187
  const err = e instanceof Error ? e : new Error(String(e));
184
188
  return createUpdateDetails(cachedResult != null, this.store.getSource(), performance.now() - startTime, cachedResult ? null : err, [...warnings, 'NetworkFetchFailed']);
185
189
  }
@@ -221,6 +225,7 @@ class WolvesClient {
221
225
  }
222
226
  this.store.setValuesFromResult(cachedResult, user);
223
227
  this.loadingStatus = 'Ready';
228
+ this.store.finalize();
224
229
  // Trigger background refresh unless disabled
225
230
  if (options === null || options === void 0 ? void 0 : options.disableBackgroundCacheRefresh) {
226
231
  return createUpdateDetails(true, this.store.getSource(), performance.now() - startTime, null, warnings);
@@ -263,7 +268,7 @@ class WolvesClient {
263
268
  }
264
269
  getExperiment(experimentName) {
265
270
  var _a, _b, _c;
266
- const config = this.store.getExperiment(experimentName);
271
+ const { config, details } = this.store.getExperiment(experimentName);
267
272
  const experiment = {
268
273
  name: experimentName,
269
274
  value: (_a = config === null || config === void 0 ? void 0 : config.value) !== null && _a !== void 0 ? _a : {},
@@ -278,8 +283,8 @@ class WolvesClient {
278
283
  return val;
279
284
  }
280
285
  };
281
- // Log exposure
282
- this.logExposure(experimentName, config);
286
+ // Log exposure with reason
287
+ this.logExposure(experimentName, config, details);
283
288
  return experiment;
284
289
  }
285
290
  /**
@@ -289,7 +294,7 @@ class WolvesClient {
289
294
  */
290
295
  getExperimentForTest(experimentName, groupName) {
291
296
  var _a;
292
- const config = this.store.getExperiment(experimentName);
297
+ const { config, details } = this.store.getExperiment(experimentName);
293
298
  const experimentId = (_a = config === null || config === void 0 ? void 0 : config.experiment_id) !== null && _a !== void 0 ? _a : '';
294
299
  const experiment = {
295
300
  name: experimentName,
@@ -301,7 +306,7 @@ class WolvesClient {
301
306
  }
302
307
  };
303
308
  // Log exposure with the specified group and experiment ID from store
304
- this.logExposure(experimentName, { group: groupName, experiment_id: experimentId, value: {} });
309
+ this.logExposure(experimentName, { group: groupName, experiment_id: experimentId, value: {} }, details);
305
310
  return experiment;
306
311
  }
307
312
  logEvent(eventName, value, metadata) {
@@ -319,7 +324,7 @@ class WolvesClient {
319
324
  yield this.logger.stop();
320
325
  });
321
326
  }
322
- logExposure(experimentName, experiment) {
327
+ logExposure(experimentName, experiment, details) {
323
328
  var _a, _b, _c;
324
329
  const exposureEvent = {
325
330
  eventName: 'exposure',
@@ -330,15 +335,10 @@ class WolvesClient {
330
335
  experiment_id: (_a = experiment === null || experiment === void 0 ? void 0 : experiment.experiment_id) !== null && _a !== void 0 ? _a : '',
331
336
  group: (_b = experiment === null || experiment === void 0 ? void 0 : experiment.group) !== null && _b !== void 0 ? _b : '',
332
337
  value: JSON.stringify((_c = experiment === null || experiment === void 0 ? void 0 : experiment.value) !== null && _c !== void 0 ? _c : {}),
338
+ reason: details.reason,
333
339
  },
334
340
  };
335
341
  this.logger.enqueue(exposureEvent);
336
342
  }
337
- /**
338
- * Get the underlying store (for testing purposes).
339
- */
340
- getStore() {
341
- return this.store;
342
- }
343
343
  }
344
344
  exports.WolvesClient = WolvesClient;
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "1.0.4";
1
+ export declare const SDK_VERSION = "1.0.5";
2
2
  export declare const SDK_TYPE = "wolves-js-client";
3
3
  export type WolvesMetadata = {
4
4
  readonly [key: string]: string | undefined | null;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WolvesMetadataProvider = exports.SDK_TYPE = exports.SDK_VERSION = void 0;
4
- exports.SDK_VERSION = '1.0.4';
4
+ exports.SDK_VERSION = '1.0.5';
5
5
  exports.SDK_TYPE = 'wolves-js-client';
6
6
  let metadata = {
7
7
  sdk_version: exports.SDK_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolves-js-client",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "A Wolves JavaScript Client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",