wolves-js-client 1.0.3 → 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.
@@ -8,6 +8,7 @@ export declare class EventLogger {
8
8
  constructor(network: Network);
9
9
  enqueue(event: WolvesEvent): void;
10
10
  flush(): Promise<void>;
11
- private start;
11
+ start(): void;
12
+ reset(): void;
12
13
  stop(): Promise<void>;
13
14
  }
@@ -58,6 +58,9 @@ class EventLogger {
58
58
  this.flush();
59
59
  }, DEFAULT_FLUSH_INTERVAL_MS);
60
60
  }
61
+ reset() {
62
+ this.queue = [];
63
+ }
61
64
  stop() {
62
65
  return __awaiter(this, void 0, void 0, function* () {
63
66
  if (this.flushIntervalId) {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Interface for storage providers that can persist data.
3
+ * Supports both synchronous and asynchronous storage implementations.
4
+ */
5
+ export interface StorageProvider {
6
+ isReady: () => boolean;
7
+ isReadyResolver: () => Promise<void> | null;
8
+ getProviderName: () => string;
9
+ getItem: (key: string) => string | null;
10
+ setItem: (key: string, value: string) => void;
11
+ removeItem: (key: string) => void;
12
+ getAllKeys: () => readonly string[];
13
+ }
14
+ interface StorageProviderManagement {
15
+ _setProvider: (newProvider: StorageProvider) => void;
16
+ _setDisabled: (isDisabled: boolean) => void;
17
+ }
18
+ /**
19
+ * Global storage singleton that automatically chooses between localStorage (browser)
20
+ * and in-memory storage (Node.js or when localStorage is unavailable).
21
+ */
22
+ export declare const Storage: StorageProvider & StorageProviderManagement;
23
+ /**
24
+ * Helper to get a parsed object from storage.
25
+ */
26
+ export declare function _getObjectFromStorage<T>(key: string): T | null;
27
+ /**
28
+ * Helper to set an object in storage as JSON string.
29
+ */
30
+ export declare function _setObjectInStorage(key: string, obj: unknown): void;
31
+ /**
32
+ * Clear the in-memory store (useful for testing).
33
+ */
34
+ export declare function _clearInMemoryStore(): void;
35
+ export {};
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Storage = void 0;
4
+ exports._getObjectFromStorage = _getObjectFromStorage;
5
+ exports._setObjectInStorage = _setObjectInStorage;
6
+ exports._clearInMemoryStore = _clearInMemoryStore;
7
+ const Log_1 = require("./Log");
8
+ const inMemoryStore = {};
9
+ /**
10
+ * In-memory storage provider for non-browser environments or when localStorage is unavailable.
11
+ */
12
+ const _inMemoryProvider = {
13
+ isReady: () => true,
14
+ isReadyResolver: () => null,
15
+ getProviderName: () => 'InMemory',
16
+ getItem: (key) => inMemoryStore[key] ? inMemoryStore[key] : null,
17
+ setItem: (key, value) => {
18
+ inMemoryStore[key] = value;
19
+ },
20
+ removeItem: (key) => {
21
+ delete inMemoryStore[key];
22
+ },
23
+ getAllKeys: () => Object.keys(inMemoryStore),
24
+ };
25
+ /**
26
+ * Helper function to safely get window object.
27
+ */
28
+ function _getWindowSafe() {
29
+ try {
30
+ if (typeof window !== 'undefined') {
31
+ return window;
32
+ }
33
+ }
34
+ catch (_a) {
35
+ // window is not defined (Node.js environment)
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Try to create a localStorage provider if available.
41
+ */
42
+ let _localStorageProvider = null;
43
+ try {
44
+ const win = _getWindowSafe();
45
+ if (win &&
46
+ win.localStorage &&
47
+ typeof win.localStorage.getItem === 'function') {
48
+ _localStorageProvider = {
49
+ isReady: () => true,
50
+ isReadyResolver: () => null,
51
+ getProviderName: () => 'LocalStorage',
52
+ getItem: (key) => win.localStorage.getItem(key),
53
+ setItem: (key, value) => win.localStorage.setItem(key, value),
54
+ removeItem: (key) => win.localStorage.removeItem(key),
55
+ getAllKeys: () => Object.keys(win.localStorage),
56
+ };
57
+ }
58
+ }
59
+ catch (_a) {
60
+ Log_1.Log.warn('Failed to setup localStorage provider');
61
+ }
62
+ let _main = _localStorageProvider !== null && _localStorageProvider !== void 0 ? _localStorageProvider : _inMemoryProvider;
63
+ let _current = _main;
64
+ /**
65
+ * Helper function that falls back to in-memory storage on SecurityError.
66
+ */
67
+ function _inMemoryBreaker(action) {
68
+ try {
69
+ return action();
70
+ }
71
+ catch (error) {
72
+ if (error instanceof Error && error.name === 'SecurityError') {
73
+ exports.Storage._setProvider(_inMemoryProvider);
74
+ return null;
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+ /**
80
+ * Global storage singleton that automatically chooses between localStorage (browser)
81
+ * and in-memory storage (Node.js or when localStorage is unavailable).
82
+ */
83
+ exports.Storage = {
84
+ isReady: () => _current.isReady(),
85
+ isReadyResolver: () => _current.isReadyResolver(),
86
+ getProviderName: () => _current.getProviderName(),
87
+ getItem: (key) => _inMemoryBreaker(() => _current.getItem(key)),
88
+ setItem: (key, value) => _inMemoryBreaker(() => _current.setItem(key, value)),
89
+ removeItem: (key) => _current.removeItem(key),
90
+ getAllKeys: () => _current.getAllKeys(),
91
+ // StorageProviderManagement
92
+ _setProvider: (newProvider) => {
93
+ _main = newProvider;
94
+ _current = newProvider;
95
+ },
96
+ _setDisabled: (isDisabled) => {
97
+ if (isDisabled) {
98
+ _current = _inMemoryProvider;
99
+ }
100
+ else {
101
+ _current = _main;
102
+ }
103
+ },
104
+ };
105
+ /**
106
+ * Helper to get a parsed object from storage.
107
+ */
108
+ function _getObjectFromStorage(key) {
109
+ const value = exports.Storage.getItem(key);
110
+ try {
111
+ return JSON.parse(value !== null && value !== void 0 ? value : 'null');
112
+ }
113
+ catch (_a) {
114
+ Log_1.Log.error(`Failed to parse value for key "${key}"`);
115
+ return null;
116
+ }
117
+ }
118
+ /**
119
+ * Helper to set an object in storage as JSON string.
120
+ */
121
+ function _setObjectInStorage(key, obj) {
122
+ exports.Storage.setItem(key, JSON.stringify(obj));
123
+ }
124
+ /**
125
+ * Clear the in-memory store (useful for testing).
126
+ */
127
+ function _clearInMemoryStore() {
128
+ for (const key of Object.keys(inMemoryStore)) {
129
+ delete inMemoryStore[key];
130
+ }
131
+ }
package/dist/Store.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { WolvesUser } from './WolvesUser';
1
2
  export type ExperimentConfig = {
2
3
  value: Record<string, any>;
3
4
  experiment_id?: string;
@@ -8,11 +9,89 @@ export type InitializeResponse = {
8
9
  has_updates: boolean;
9
10
  time: number;
10
11
  };
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
+ };
22
+ export type DataAdapterResult = {
23
+ source: DataSource;
24
+ data: string;
25
+ receivedAt: number;
26
+ fullUserHash?: string;
27
+ };
28
+ /**
29
+ * Store class that manages experiment configurations with caching support.
30
+ * Supports localStorage in browser environments and in-memory storage elsewhere.
31
+ */
11
32
  export declare class Store {
12
33
  private values;
13
- constructor();
14
- setValues(values: InitializeResponse): void;
15
- getExperiment(name: string): ExperimentConfig | null;
34
+ private source;
35
+ private isInitialized;
36
+ private sdkKey;
37
+ private cacheLimit;
38
+ private inMemoryCache;
39
+ constructor(sdkKey?: string);
40
+ /**
41
+ * Get the cache key for a specific user.
42
+ */
43
+ getCacheKey(user?: WolvesUser): string;
44
+ /**
45
+ * Load cached data synchronously.
46
+ * First checks in-memory cache, then persistent storage.
47
+ */
48
+ getDataSync(user?: WolvesUser): DataAdapterResult | null;
49
+ /**
50
+ * Load from persistent storage cache.
51
+ */
52
+ private _loadFromCache;
53
+ /**
54
+ * Set values from a data adapter result.
55
+ */
56
+ setValuesFromResult(result: DataAdapterResult | null, user?: WolvesUser): boolean;
57
+ /**
58
+ * Set values directly from an InitializeResponse (legacy method).
59
+ */
60
+ setValues(values: InitializeResponse, user?: WolvesUser): void;
61
+ /**
62
+ * Write to persistent cache with eviction support.
63
+ */
64
+ private _writeToCache;
65
+ /**
66
+ * Run cache eviction to stay within limits.
67
+ */
68
+ private _runCacheEviction;
69
+ /**
70
+ * Reset the store for a new user.
71
+ */
72
+ reset(): void;
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;
16
90
  getValues(): InitializeResponse | null;
91
+ getSource(): DataSource;
17
92
  getLastUpdateTime(): number | undefined;
93
+ /**
94
+ * Clear in-memory cache (useful for testing).
95
+ */
96
+ clearInMemoryCache(): void;
18
97
  }
package/dist/Store.js CHANGED
@@ -1,25 +1,247 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Store = void 0;
4
+ const StorageProvider_1 = require("./StorageProvider");
5
+ const Log_1 = require("./Log");
6
+ const CACHE_KEY_PREFIX = 'wolves.cache';
7
+ const CACHE_LIMIT = 10;
8
+ const MAX_CACHE_WRITE_ATTEMPTS = 8;
9
+ const LAST_MODIFIED_KEY = 'wolves.last_modified_time';
10
+ /**
11
+ * Generate a user identifier for cache keys.
12
+ * Only uses userID as the unique identifier.
13
+ */
14
+ function _getUserIdentifier(user) {
15
+ if (!user || !user.userID)
16
+ return 'anonymous';
17
+ return user.userID;
18
+ }
19
+ /**
20
+ * Store class that manages experiment configurations with caching support.
21
+ * Supports localStorage in browser environments and in-memory storage elsewhere.
22
+ */
4
23
  class Store {
5
- constructor() {
24
+ constructor(sdkKey = '') {
6
25
  this.values = null;
26
+ this.source = 'NoValues';
27
+ this.isInitialized = false;
28
+ this.cacheLimit = CACHE_LIMIT;
29
+ this.inMemoryCache = new Map();
30
+ this.sdkKey = sdkKey;
31
+ }
32
+ /**
33
+ * Get the cache key for a specific user.
34
+ */
35
+ getCacheKey(user) {
36
+ const userHash = _getUserIdentifier(user);
37
+ return `${CACHE_KEY_PREFIX}.${this.sdkKey}.${userHash}`;
38
+ }
39
+ /**
40
+ * Load cached data synchronously.
41
+ * First checks in-memory cache, then persistent storage.
42
+ */
43
+ getDataSync(user) {
44
+ const cacheKey = this.getCacheKey(user);
45
+ // Check in-memory cache first
46
+ const inMemResult = this.inMemoryCache.get(cacheKey);
47
+ if (inMemResult) {
48
+ return inMemResult;
49
+ }
50
+ // Check persistent storage
51
+ const cached = this._loadFromCache(cacheKey);
52
+ if (cached) {
53
+ this.inMemoryCache.set(cacheKey, cached);
54
+ return cached;
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Load from persistent storage cache.
60
+ */
61
+ _loadFromCache(cacheKey) {
62
+ const cache = StorageProvider_1.Storage.getItem(cacheKey);
63
+ if (cache == null) {
64
+ return null;
65
+ }
66
+ try {
67
+ const result = JSON.parse(cache);
68
+ if (result && result.source) {
69
+ return Object.assign(Object.assign({}, result), { source: 'Cache' });
70
+ }
71
+ }
72
+ catch (_a) {
73
+ Log_1.Log.error(`Failed to parse cached value for key "${cacheKey}"`);
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Set values from a data adapter result.
79
+ */
80
+ setValuesFromResult(result, user) {
81
+ if (!result || !result.data) {
82
+ return false;
83
+ }
84
+ try {
85
+ const parsed = JSON.parse(result.data);
86
+ this.values = parsed;
87
+ this.source = result.source;
88
+ // Update caches
89
+ const cacheKey = this.getCacheKey(user);
90
+ this.inMemoryCache.set(cacheKey, result);
91
+ // Persist to storage if it's from network
92
+ if (result.source === 'Network' || result.source === 'NetworkNotModified') {
93
+ this._writeToCache(cacheKey, result);
94
+ }
95
+ return true;
96
+ }
97
+ catch (_a) {
98
+ Log_1.Log.error('Failed to parse data adapter result');
99
+ return false;
100
+ }
7
101
  }
8
- setValues(values) {
102
+ /**
103
+ * Set values directly from an InitializeResponse (legacy method).
104
+ */
105
+ setValues(values, user) {
9
106
  this.values = values;
107
+ this.source = 'Network';
108
+ // Create and cache the result
109
+ const result = {
110
+ source: 'Network',
111
+ data: JSON.stringify(values),
112
+ receivedAt: Date.now(),
113
+ fullUserHash: _getUserIdentifier(user),
114
+ };
115
+ const cacheKey = this.getCacheKey(user);
116
+ this.inMemoryCache.set(cacheKey, result);
117
+ this._writeToCache(cacheKey, result);
10
118
  }
119
+ /**
120
+ * Write to persistent cache with eviction support.
121
+ */
122
+ _writeToCache(cacheKey, result) {
123
+ const resultString = JSON.stringify(result);
124
+ for (let i = 0; i < MAX_CACHE_WRITE_ATTEMPTS; i++) {
125
+ try {
126
+ StorageProvider_1.Storage.setItem(cacheKey, resultString);
127
+ break;
128
+ }
129
+ catch (error) {
130
+ if (!(error instanceof Error) ||
131
+ !(error.toString().includes('QuotaExceededError') ||
132
+ error.toString().includes('QUOTA_EXCEEDED_ERR')) ||
133
+ this.cacheLimit <= 1) {
134
+ Log_1.Log.warn('Failed to write to cache', error);
135
+ return;
136
+ }
137
+ this.cacheLimit = Math.ceil(this.cacheLimit / 2);
138
+ this._runCacheEviction(cacheKey, this.cacheLimit - 1);
139
+ }
140
+ }
141
+ this._runCacheEviction(cacheKey);
142
+ }
143
+ /**
144
+ * Run cache eviction to stay within limits.
145
+ */
146
+ _runCacheEviction(cacheKey, limit = this.cacheLimit) {
147
+ var _a;
148
+ const lastModifiedTimeMap = (_a = (0, StorageProvider_1._getObjectFromStorage)(LAST_MODIFIED_KEY)) !== null && _a !== void 0 ? _a : {};
149
+ lastModifiedTimeMap[cacheKey] = Date.now();
150
+ const keys = Object.keys(lastModifiedTimeMap);
151
+ if (keys.length > limit) {
152
+ // Sort by time and remove oldest
153
+ const sortedKeys = keys.sort((a, b) => lastModifiedTimeMap[a] - lastModifiedTimeMap[b]);
154
+ const keysToEvict = sortedKeys.slice(0, keys.length - limit);
155
+ for (const evictKey of keysToEvict) {
156
+ delete lastModifiedTimeMap[evictKey];
157
+ StorageProvider_1.Storage.removeItem(evictKey);
158
+ }
159
+ }
160
+ (0, StorageProvider_1._setObjectInStorage)(LAST_MODIFIED_KEY, lastModifiedTimeMap);
161
+ }
162
+ /**
163
+ * Reset the store for a new user.
164
+ */
165
+ reset() {
166
+ this.values = null;
167
+ this.source = 'NoValues';
168
+ this.isInitialized = false;
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
+ */
11
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
12
197
  if (!this.values || !this.values.dynamic_configs) {
13
- return null;
198
+ return {
199
+ config: null,
200
+ details: {
201
+ source: this.source,
202
+ reason: 'NoValues',
203
+ isRecognized: false,
204
+ },
205
+ };
14
206
  }
15
- 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;
16
229
  }
17
230
  getValues() {
18
231
  return this.values;
19
232
  }
233
+ getSource() {
234
+ return this.source;
235
+ }
20
236
  getLastUpdateTime() {
21
237
  var _a;
22
238
  return (_a = this.values) === null || _a === void 0 ? void 0 : _a.time;
23
239
  }
240
+ /**
241
+ * Clear in-memory cache (useful for testing).
242
+ */
243
+ clearInMemoryCache() {
244
+ this.inMemoryCache.clear();
245
+ }
24
246
  }
25
247
  exports.Store = Store;
package/dist/Types.d.ts CHANGED
@@ -1,8 +1,4 @@
1
1
  import { WolvesUser } from './WolvesUser';
2
- export type WolvesUpdateDetails = {
3
- success: boolean;
4
- errorMessage?: string;
5
- };
6
2
  export type WolvesEvent = {
7
3
  eventName: string;
8
4
  value?: string | number;
@@ -1,17 +1,102 @@
1
1
  import { WolvesUser } from './WolvesUser';
2
- import { Experiment, WolvesUpdateDetails } from './Types';
2
+ import { DataSource } from './Store';
3
+ import { Experiment } from './Types';
4
+ export type LoadingStatus = 'Uninitialized' | 'Loading' | 'Ready';
5
+ export type WolvesUpdateDetails = {
6
+ success: boolean;
7
+ source: DataSource;
8
+ duration: number;
9
+ error: Error | null;
10
+ warnings: string[];
11
+ };
12
+ export type SyncUpdateOptions = {
13
+ disableBackgroundCacheRefresh?: boolean;
14
+ };
15
+ export type AsyncUpdateOptions = {
16
+ timeoutMs?: number;
17
+ };
3
18
  export declare class WolvesClient {
4
19
  private sdkKey;
5
20
  private user;
6
21
  private network;
7
22
  private store;
8
23
  private logger;
9
- private initialized;
24
+ private loadingStatus;
25
+ private initializePromise;
10
26
  constructor(sdkKey: string, user: WolvesUser);
11
- initializeAsync(): Promise<WolvesUpdateDetails>;
12
- initializeSync(): WolvesUpdateDetails;
13
- updateUserAsync(user: WolvesUser): Promise<WolvesUpdateDetails>;
14
- updateUserSync(user: WolvesUser): WolvesUpdateDetails;
27
+ /**
28
+ * Returns the current loading status of the client.
29
+ */
30
+ getLoadingStatus(): LoadingStatus;
31
+ /**
32
+ * Initializes the WolvesClient asynchronously by first using cached values
33
+ * and then updating to the latest values from the network.
34
+ *
35
+ * @param options - Optional settings including timeout
36
+ * @returns Promise that resolves once initialized with latest values or timeout is hit
37
+ */
38
+ initializeAsync(options?: AsyncUpdateOptions): Promise<WolvesUpdateDetails>;
39
+ /**
40
+ * Internal implementation of async initialization.
41
+ */
42
+ private _initializeAsyncImpl;
43
+ /**
44
+ * Initializes the WolvesClient synchronously using cached values.
45
+ * After initialization, cache values are updated in the background for future use.
46
+ * This is useful for quickly starting with last-known-good configurations while
47
+ * refreshing data in the background.
48
+ *
49
+ * Aligned with Statsig SDK behavior:
50
+ * - If no cached values exist, returns with NoCachedValues warning
51
+ * - Triggers background network fetch to update cache for next session
52
+ *
53
+ * @param options - Optional settings
54
+ * @returns Update details including success status and any warnings
55
+ */
56
+ initializeSync(options?: SyncUpdateOptions): WolvesUpdateDetails;
57
+ /**
58
+ * Asynchronously updates the user by first using cached values and then fetching
59
+ * the latest values from the network.
60
+ *
61
+ * @param user - The new user to switch to
62
+ * @param options - Optional settings including timeout
63
+ * @returns Promise that resolves once updated with latest values or timeout is hit
64
+ */
65
+ updateUserAsync(user: WolvesUser, options?: AsyncUpdateOptions): Promise<WolvesUpdateDetails>;
66
+ /**
67
+ * Internal implementation of async user update.
68
+ */
69
+ private _updateUserAsyncImpl;
70
+ /**
71
+ * Fetch from network and update the store.
72
+ */
73
+ private _fetchAndUpdateFromNetwork;
74
+ /**
75
+ * Synchronously updates the user in the client and switches to cached values.
76
+ * After the initial switch to cached values, updates values in the background.
77
+ *
78
+ * Aligned with Statsig SDK behavior:
79
+ * - Immediately returns with cached values if available
80
+ * - If no cached values exist, returns with NoCachedValues warning
81
+ * - Triggers background network fetch (unless disableBackgroundCacheRefresh is true)
82
+ *
83
+ * @param user - The new user to switch to
84
+ * @param options - Optional settings
85
+ * @returns Update details including success status and any warnings
86
+ */
87
+ updateUserSync(user: WolvesUser, options?: SyncUpdateOptions): WolvesUpdateDetails;
88
+ /**
89
+ * Internal implementation of sync user update.
90
+ */
91
+ private _updateUserSyncImpl;
92
+ /**
93
+ * Run a background update to refresh the cache.
94
+ */
95
+ private _runBackgroundUpdate;
96
+ /**
97
+ * Reset the store and logger for a new user.
98
+ */
99
+ private _resetForUser;
15
100
  getExperiment(experimentName: string): Experiment;
16
101
  /**
17
102
  * Test-only method to manually trigger an exposure event with a specified group.
@@ -13,64 +13,262 @@ exports.WolvesClient = void 0;
13
13
  const Network_1 = require("./Network");
14
14
  const Store_1 = require("./Store");
15
15
  const EventLogger_1 = require("./EventLogger");
16
+ const StorageProvider_1 = require("./StorageProvider");
17
+ const Log_1 = require("./Log");
18
+ /**
19
+ * Creates a WolvesUpdateDetails result object.
20
+ */
21
+ function createUpdateDetails(success, source, duration, error, warnings = []) {
22
+ return { success, source, duration, error, warnings };
23
+ }
16
24
  class WolvesClient {
17
25
  constructor(sdkKey, user) {
18
- this.initialized = false;
26
+ this.loadingStatus = 'Uninitialized';
27
+ this.initializePromise = null;
19
28
  this.sdkKey = sdkKey;
20
29
  this.user = user;
21
30
  this.network = new Network_1.Network(sdkKey);
22
- this.store = new Store_1.Store();
31
+ this.store = new Store_1.Store(sdkKey);
23
32
  this.logger = new EventLogger_1.EventLogger(this.network);
24
33
  }
25
- initializeAsync() {
34
+ /**
35
+ * Returns the current loading status of the client.
36
+ */
37
+ getLoadingStatus() {
38
+ return this.loadingStatus;
39
+ }
40
+ /**
41
+ * Initializes the WolvesClient asynchronously by first using cached values
42
+ * and then updating to the latest values from the network.
43
+ *
44
+ * @param options - Optional settings including timeout
45
+ * @returns Promise that resolves once initialized with latest values or timeout is hit
46
+ */
47
+ initializeAsync(options) {
26
48
  return __awaiter(this, void 0, void 0, function* () {
27
- const config = yield this.network.fetchConfig(this.user, this.store.getLastUpdateTime());
28
- if (config) {
29
- this.store.setValues(config);
30
- this.initialized = true;
31
- return { success: true };
49
+ if (this.initializePromise) {
50
+ return this.initializePromise;
32
51
  }
33
- return { success: false, errorMessage: 'Failed to fetch config' };
52
+ this.initializePromise = this._initializeAsyncImpl(options);
53
+ return this.initializePromise;
34
54
  });
35
55
  }
36
- initializeSync() {
37
- // In a real implementation, this might load from local storage or cache.
38
- // For this minimal implementation, we trigger a background fetch.
39
- this.network.fetchConfig(this.user, this.store.getLastUpdateTime()).then((config) => {
40
- if (config) {
41
- this.store.setValues(config);
56
+ /**
57
+ * Internal implementation of async initialization.
58
+ */
59
+ _initializeAsyncImpl(options) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ if (!StorageProvider_1.Storage.isReady()) {
62
+ const resolver = StorageProvider_1.Storage.isReadyResolver();
63
+ if (resolver) {
64
+ yield resolver;
65
+ }
42
66
  }
67
+ this.logger.start();
68
+ return this.updateUserAsync(this.user, options);
43
69
  });
44
- this.initialized = true;
45
- return { success: true };
46
70
  }
47
- updateUserAsync(user) {
71
+ /**
72
+ * Initializes the WolvesClient synchronously using cached values.
73
+ * After initialization, cache values are updated in the background for future use.
74
+ * This is useful for quickly starting with last-known-good configurations while
75
+ * refreshing data in the background.
76
+ *
77
+ * Aligned with Statsig SDK behavior:
78
+ * - If no cached values exist, returns with NoCachedValues warning
79
+ * - Triggers background network fetch to update cache for next session
80
+ *
81
+ * @param options - Optional settings
82
+ * @returns Update details including success status and any warnings
83
+ */
84
+ initializeSync(options) {
85
+ if (this.loadingStatus !== 'Uninitialized') {
86
+ return createUpdateDetails(true, this.store.getSource(), -1, null, ['MultipleInitializations']);
87
+ }
88
+ this.logger.start();
89
+ return this.updateUserSync(this.user, options);
90
+ }
91
+ /**
92
+ * Asynchronously updates the user by first using cached values and then fetching
93
+ * the latest values from the network.
94
+ *
95
+ * @param user - The new user to switch to
96
+ * @param options - Optional settings including timeout
97
+ * @returns Promise that resolves once updated with latest values or timeout is hit
98
+ */
99
+ updateUserAsync(user, options) {
48
100
  return __awaiter(this, void 0, void 0, function* () {
49
- this.user = user;
50
- this.store = new Store_1.Store(); // Reset store for new user
51
- const config = yield this.network.fetchConfig(this.user);
52
- if (config) {
53
- this.store.setValues(config);
54
- return { success: true };
101
+ const startTime = performance.now();
102
+ try {
103
+ return yield this._updateUserAsyncImpl(user, options, startTime);
104
+ }
105
+ catch (e) {
106
+ const err = e instanceof Error ? e : new Error(String(e));
107
+ return createUpdateDetails(false, this.store.getSource(), performance.now() - startTime, err);
55
108
  }
56
- return { success: false, errorMessage: 'Failed to fetch config' };
57
109
  });
58
110
  }
59
- updateUserSync(user) {
60
- this.user = user;
61
- this.store = new Store_1.Store(); // Reset store for new user
62
- // In a real implementation, this might load from local storage or cache.
63
- // For this minimal implementation, we trigger a background fetch.
64
- this.network.fetchConfig(this.user).then((config) => {
111
+ /**
112
+ * Internal implementation of async user update.
113
+ */
114
+ _updateUserAsyncImpl(user, options, startTime) {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ this._resetForUser(user);
117
+ const initiator = this.user;
118
+ const warnings = [];
119
+ // First, try to load from cache
120
+ let cachedResult = this.store.getDataSync(user);
121
+ if (cachedResult) {
122
+ this.store.setValuesFromResult(cachedResult, user);
123
+ }
124
+ else {
125
+ warnings.push('NoCachedValues');
126
+ }
127
+ this.loadingStatus = 'Loading';
128
+ // Fetch from network
129
+ const ops = [
130
+ this._fetchAndUpdateFromNetwork(user, cachedResult, startTime, warnings)
131
+ ];
132
+ if (options === null || options === void 0 ? void 0 : options.timeoutMs) {
133
+ ops.push(new Promise((resolve) => {
134
+ setTimeout(() => {
135
+ Log_1.Log.debug('Fetching latest value timed out');
136
+ resolve(null);
137
+ }, options.timeoutMs);
138
+ }));
139
+ }
140
+ const result = yield Promise.race(ops);
141
+ // Check if user changed during update
142
+ if (initiator !== this.user) {
143
+ return createUpdateDetails(false, this.store.getSource(), performance.now() - startTime, new Error('User changed during update'), warnings);
144
+ }
145
+ if (result) {
146
+ return result;
147
+ }
148
+ // Timeout case - return with cached values
149
+ this.loadingStatus = 'Ready';
150
+ this.store.finalize();
151
+ return createUpdateDetails(cachedResult != null, this.store.getSource(), performance.now() - startTime, cachedResult ? null : new Error('Timeout waiting for network'), warnings);
152
+ });
153
+ }
154
+ /**
155
+ * Fetch from network and update the store.
156
+ */
157
+ _fetchAndUpdateFromNetwork(user, cachedResult, startTime, warnings) {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ try {
160
+ const config = yield this.network.fetchConfig(user, this.store.getLastUpdateTime());
161
+ if (config) {
162
+ if (config.has_updates === true || config.has_updates === undefined) {
163
+ const networkResult = {
164
+ source: 'Network',
165
+ data: JSON.stringify(config),
166
+ receivedAt: Date.now(),
167
+ };
168
+ this.store.setValuesFromResult(networkResult, user);
169
+ }
170
+ else if (cachedResult && config.has_updates === false) {
171
+ // No updates, but we have cached values - mark as NetworkNotModified
172
+ const notModifiedResult = Object.assign(Object.assign({}, cachedResult), { source: 'NetworkNotModified' });
173
+ this.store.setValuesFromResult(notModifiedResult, user);
174
+ }
175
+ this.loadingStatus = 'Ready';
176
+ this.store.finalize();
177
+ return createUpdateDetails(true, this.store.getSource(), performance.now() - startTime, null, warnings);
178
+ }
179
+ // Network fetch failed
180
+ this.loadingStatus = 'Ready';
181
+ this.store.finalize();
182
+ return createUpdateDetails(cachedResult != null, this.store.getSource(), performance.now() - startTime, cachedResult ? null : new Error('Failed to fetch config from network'), [...warnings, 'NetworkFetchFailed']);
183
+ }
184
+ catch (e) {
185
+ this.loadingStatus = 'Ready';
186
+ this.store.finalize();
187
+ const err = e instanceof Error ? e : new Error(String(e));
188
+ return createUpdateDetails(cachedResult != null, this.store.getSource(), performance.now() - startTime, cachedResult ? null : err, [...warnings, 'NetworkFetchFailed']);
189
+ }
190
+ });
191
+ }
192
+ /**
193
+ * Synchronously updates the user in the client and switches to cached values.
194
+ * After the initial switch to cached values, updates values in the background.
195
+ *
196
+ * Aligned with Statsig SDK behavior:
197
+ * - Immediately returns with cached values if available
198
+ * - If no cached values exist, returns with NoCachedValues warning
199
+ * - Triggers background network fetch (unless disableBackgroundCacheRefresh is true)
200
+ *
201
+ * @param user - The new user to switch to
202
+ * @param options - Optional settings
203
+ * @returns Update details including success status and any warnings
204
+ */
205
+ updateUserSync(user, options) {
206
+ const startTime = performance.now();
207
+ try {
208
+ return this._updateUserSyncImpl(user, options, startTime);
209
+ }
210
+ catch (e) {
211
+ const err = e instanceof Error ? e : new Error(String(e));
212
+ return createUpdateDetails(false, this.store.getSource(), performance.now() - startTime, err);
213
+ }
214
+ }
215
+ /**
216
+ * Internal implementation of sync user update.
217
+ */
218
+ _updateUserSyncImpl(user, options, startTime) {
219
+ const warnings = [];
220
+ this._resetForUser(user);
221
+ // Load from cache synchronously
222
+ const cachedResult = this.store.getDataSync(user);
223
+ if (cachedResult == null) {
224
+ warnings.push('NoCachedValues');
225
+ }
226
+ this.store.setValuesFromResult(cachedResult, user);
227
+ this.loadingStatus = 'Ready';
228
+ this.store.finalize();
229
+ // Trigger background refresh unless disabled
230
+ if (options === null || options === void 0 ? void 0 : options.disableBackgroundCacheRefresh) {
231
+ return createUpdateDetails(true, this.store.getSource(), performance.now() - startTime, null, warnings);
232
+ }
233
+ this._runBackgroundUpdate(user, cachedResult);
234
+ return createUpdateDetails(true, this.store.getSource(), performance.now() - startTime, null, warnings);
235
+ }
236
+ /**
237
+ * Run a background update to refresh the cache.
238
+ */
239
+ _runBackgroundUpdate(user, cachedResult) {
240
+ this.network.fetchConfig(user, this.store.getLastUpdateTime())
241
+ .then((config) => {
65
242
  if (config) {
66
- this.store.setValues(config);
243
+ if (config.has_updates === true || config.has_updates === undefined) {
244
+ const networkResult = {
245
+ source: 'Network',
246
+ data: JSON.stringify(config),
247
+ receivedAt: Date.now(),
248
+ };
249
+ this.store.setValuesFromResult(networkResult, user);
250
+ }
251
+ else if (cachedResult && config.has_updates === false) {
252
+ const notModifiedResult = Object.assign(Object.assign({}, cachedResult), { source: 'NetworkNotModified' });
253
+ this.store.setValuesFromResult(notModifiedResult, user);
254
+ }
67
255
  }
256
+ })
257
+ .catch((err) => {
258
+ Log_1.Log.error('Background update failed', err);
68
259
  });
69
- return { success: true };
260
+ }
261
+ /**
262
+ * Reset the store and logger for a new user.
263
+ */
264
+ _resetForUser(user) {
265
+ this.logger.reset();
266
+ this.store.reset();
267
+ this.user = user;
70
268
  }
71
269
  getExperiment(experimentName) {
72
270
  var _a, _b, _c;
73
- const config = this.store.getExperiment(experimentName);
271
+ const { config, details } = this.store.getExperiment(experimentName);
74
272
  const experiment = {
75
273
  name: experimentName,
76
274
  value: (_a = config === null || config === void 0 ? void 0 : config.value) !== null && _a !== void 0 ? _a : {},
@@ -85,8 +283,8 @@ class WolvesClient {
85
283
  return val;
86
284
  }
87
285
  };
88
- // Log exposure
89
- this.logExposure(experimentName, config);
286
+ // Log exposure with reason
287
+ this.logExposure(experimentName, config, details);
90
288
  return experiment;
91
289
  }
92
290
  /**
@@ -96,7 +294,7 @@ class WolvesClient {
96
294
  */
97
295
  getExperimentForTest(experimentName, groupName) {
98
296
  var _a;
99
- const config = this.store.getExperiment(experimentName);
297
+ const { config, details } = this.store.getExperiment(experimentName);
100
298
  const experimentId = (_a = config === null || config === void 0 ? void 0 : config.experiment_id) !== null && _a !== void 0 ? _a : '';
101
299
  const experiment = {
102
300
  name: experimentName,
@@ -108,7 +306,7 @@ class WolvesClient {
108
306
  }
109
307
  };
110
308
  // Log exposure with the specified group and experiment ID from store
111
- this.logExposure(experimentName, { group: groupName, experiment_id: experimentId, value: {} });
309
+ this.logExposure(experimentName, { group: groupName, experiment_id: experimentId, value: {} }, details);
112
310
  return experiment;
113
311
  }
114
312
  logEvent(eventName, value, metadata) {
@@ -126,7 +324,7 @@ class WolvesClient {
126
324
  yield this.logger.stop();
127
325
  });
128
326
  }
129
- logExposure(experimentName, experiment) {
327
+ logExposure(experimentName, experiment, details) {
130
328
  var _a, _b, _c;
131
329
  const exposureEvent = {
132
330
  eventName: 'exposure',
@@ -137,6 +335,7 @@ class WolvesClient {
137
335
  experiment_id: (_a = experiment === null || experiment === void 0 ? void 0 : experiment.experiment_id) !== null && _a !== void 0 ? _a : '',
138
336
  group: (_b = experiment === null || experiment === void 0 ? void 0 : experiment.group) !== null && _b !== void 0 ? _b : '',
139
337
  value: JSON.stringify((_c = experiment === null || experiment === void 0 ? void 0 : experiment.value) !== null && _c !== void 0 ? _c : {}),
338
+ reason: details.reason,
140
339
  },
141
340
  };
142
341
  this.logger.enqueue(exposureEvent);
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "1.0.3";
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.3';
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/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export { WolvesClient } from './WolvesClient';
2
+ export type { WolvesUpdateDetails, SyncUpdateOptions, AsyncUpdateOptions, LoadingStatus } from './WolvesClient';
2
3
  export { WolvesUser } from './WolvesUser';
3
4
  export { Experiment } from './Types';
4
5
  export { Log, LogLevel } from './Log';
5
6
  export { SDK_VERSION, SDK_TYPE, WolvesMetadataProvider } from './WolvesMetadata';
6
7
  export type { WolvesMetadata } from './WolvesMetadata';
8
+ export { Storage } from './StorageProvider';
9
+ export type { StorageProvider } from './StorageProvider';
10
+ export type { DataSource, DataAdapterResult } from './Store';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WolvesMetadataProvider = exports.SDK_TYPE = exports.SDK_VERSION = exports.LogLevel = exports.Log = exports.WolvesClient = void 0;
3
+ exports.Storage = exports.WolvesMetadataProvider = exports.SDK_TYPE = exports.SDK_VERSION = exports.LogLevel = exports.Log = exports.WolvesClient = void 0;
4
4
  var WolvesClient_1 = require("./WolvesClient");
5
5
  Object.defineProperty(exports, "WolvesClient", { enumerable: true, get: function () { return WolvesClient_1.WolvesClient; } });
6
6
  var Log_1 = require("./Log");
@@ -10,3 +10,5 @@ var WolvesMetadata_1 = require("./WolvesMetadata");
10
10
  Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return WolvesMetadata_1.SDK_VERSION; } });
11
11
  Object.defineProperty(exports, "SDK_TYPE", { enumerable: true, get: function () { return WolvesMetadata_1.SDK_TYPE; } });
12
12
  Object.defineProperty(exports, "WolvesMetadataProvider", { enumerable: true, get: function () { return WolvesMetadata_1.WolvesMetadataProvider; } });
13
+ var StorageProvider_1 = require("./StorageProvider");
14
+ Object.defineProperty(exports, "Storage", { enumerable: true, get: function () { return StorageProvider_1.Storage; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolves-js-client",
3
- "version": "1.0.3",
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",
@@ -1,22 +0,0 @@
1
- /**
2
- * DeviceInfo - Collects device and environment information for event metadata
3
- * Aligns with Statsig SDK's approach: sends raw user_agent for server-side parsing
4
- *
5
- * Note: Browser/OS parsing is done server-side from user_agent, not client-side
6
- */
7
- export type DeviceInfoData = {
8
- user_agent: string;
9
- locale: string;
10
- screen_width: number | null;
11
- screen_height: number | null;
12
- viewport_width: number | null;
13
- viewport_height: number | null;
14
- timezone: string;
15
- timezone_offset: number | null;
16
- };
17
- export declare function getDeviceInfo(): DeviceInfoData;
18
- /**
19
- * Get device info as a flat record suitable for event metadata
20
- * Only includes non-null values
21
- */
22
- export declare function getDeviceInfoForMetadata(): Record<string, string | number>;
@@ -1,98 +0,0 @@
1
- "use strict";
2
- /**
3
- * DeviceInfo - Collects device and environment information for event metadata
4
- * Aligns with Statsig SDK's approach: sends raw user_agent for server-side parsing
5
- *
6
- * Note: Browser/OS parsing is done server-side from user_agent, not client-side
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.getDeviceInfo = getDeviceInfo;
10
- exports.getDeviceInfoForMetadata = getDeviceInfoForMetadata;
11
- function getWindowSafe() {
12
- if (typeof window !== 'undefined') {
13
- return window;
14
- }
15
- return null;
16
- }
17
- function getNavigatorSafe() {
18
- var _a;
19
- const win = getWindowSafe();
20
- return (_a = win === null || win === void 0 ? void 0 : win.navigator) !== null && _a !== void 0 ? _a : null;
21
- }
22
- function getSafeTimezone() {
23
- try {
24
- return Intl.DateTimeFormat().resolvedOptions().timeZone || '';
25
- }
26
- catch (_a) {
27
- return '';
28
- }
29
- }
30
- function getSafeTimezoneOffset() {
31
- try {
32
- return new Date().getTimezoneOffset();
33
- }
34
- catch (_a) {
35
- return null;
36
- }
37
- }
38
- let cachedDeviceInfo = null;
39
- function getDeviceInfo() {
40
- var _a, _b, _c, _d, _e, _f;
41
- // Return cached info if available
42
- if (cachedDeviceInfo) {
43
- return cachedDeviceInfo;
44
- }
45
- const win = getWindowSafe();
46
- const nav = getNavigatorSafe();
47
- if (!win || !nav) {
48
- // Return empty values for server-side or non-browser environments
49
- return {
50
- user_agent: '',
51
- locale: '',
52
- screen_width: null,
53
- screen_height: null,
54
- viewport_width: null,
55
- viewport_height: null,
56
- timezone: '',
57
- timezone_offset: null,
58
- };
59
- }
60
- const userAgent = nav.userAgent || '';
61
- cachedDeviceInfo = {
62
- // Send raw user_agent - server will parse browser/os info
63
- user_agent: userAgent.length > 500 ? userAgent.substring(0, 500) : userAgent,
64
- locale: nav.language || '',
65
- screen_width: (_b = (_a = win.screen) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : null,
66
- screen_height: (_d = (_c = win.screen) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : null,
67
- viewport_width: (_e = win.innerWidth) !== null && _e !== void 0 ? _e : null,
68
- viewport_height: (_f = win.innerHeight) !== null && _f !== void 0 ? _f : null,
69
- timezone: getSafeTimezone(),
70
- timezone_offset: getSafeTimezoneOffset(),
71
- };
72
- return cachedDeviceInfo;
73
- }
74
- /**
75
- * Get device info as a flat record suitable for event metadata
76
- * Only includes non-null values
77
- */
78
- function getDeviceInfoForMetadata() {
79
- const info = getDeviceInfo();
80
- const result = {};
81
- if (info.user_agent)
82
- result['user_agent'] = info.user_agent;
83
- if (info.locale)
84
- result['locale'] = info.locale;
85
- if (info.screen_width !== null)
86
- result['screen_width'] = info.screen_width;
87
- if (info.screen_height !== null)
88
- result['screen_height'] = info.screen_height;
89
- if (info.viewport_width !== null)
90
- result['viewport_width'] = info.viewport_width;
91
- if (info.viewport_height !== null)
92
- result['viewport_height'] = info.viewport_height;
93
- if (info.timezone)
94
- result['timezone'] = info.timezone;
95
- if (info.timezone_offset !== null)
96
- result['timezone_offset'] = info.timezone_offset;
97
- return result;
98
- }
@@ -1,15 +0,0 @@
1
- import { StatsigUser } from './StatsigUser';
2
- import { Experiment, StatsigUpdateDetails } from './Types';
3
- export declare class StatsigClient {
4
- private sdkKey;
5
- private user;
6
- private network;
7
- private store;
8
- private initialized;
9
- constructor(sdkKey: string, user: StatsigUser);
10
- initializeAsync(): Promise<StatsigUpdateDetails>;
11
- initializeSync(): StatsigUpdateDetails;
12
- getExperiment(experimentName: string): Experiment;
13
- logEvent(eventName: string, value?: string | number, metadata?: Record<string, string>): void;
14
- private logExposure;
15
- }
@@ -1,92 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.StatsigClient = void 0;
13
- const Network_1 = require("./Network");
14
- const Store_1 = require("./Store");
15
- class StatsigClient {
16
- constructor(sdkKey, user) {
17
- this.initialized = false;
18
- this.sdkKey = sdkKey;
19
- this.user = user;
20
- this.network = new Network_1.Network(sdkKey);
21
- this.store = new Store_1.Store();
22
- }
23
- initializeAsync() {
24
- return __awaiter(this, void 0, void 0, function* () {
25
- const config = yield this.network.fetchConfig(this.user);
26
- if (config) {
27
- this.store.setValues(config);
28
- this.initialized = true;
29
- return { success: true };
30
- }
31
- return { success: false, errorMessage: 'Failed to fetch config' };
32
- });
33
- }
34
- initializeSync() {
35
- // In a real implementation, this might load from local storage or cache.
36
- // For this minimal implementation, we trigger a background fetch.
37
- this.network.fetchConfig(this.user).then((config) => {
38
- if (config) {
39
- this.store.setValues(config);
40
- }
41
- });
42
- this.initialized = true;
43
- return { success: true };
44
- }
45
- getExperiment(experimentName) {
46
- var _a, _b, _c;
47
- const config = this.store.getExperiment(experimentName);
48
- const experiment = {
49
- name: experimentName,
50
- value: (_a = config === null || config === void 0 ? void 0 : config.value) !== null && _a !== void 0 ? _a : {},
51
- ruleID: (_b = config === null || config === void 0 ? void 0 : config.rule_id) !== null && _b !== void 0 ? _b : '',
52
- groupName: (_c = config === null || config === void 0 ? void 0 : config.group) !== null && _c !== void 0 ? _c : null,
53
- get: (key, defaultValue) => {
54
- var _a;
55
- const val = (_a = config === null || config === void 0 ? void 0 : config.value) === null || _a === void 0 ? void 0 : _a[key];
56
- if (val === undefined || val === null) {
57
- return defaultValue;
58
- }
59
- return val;
60
- }
61
- };
62
- // Log exposure
63
- this.logExposure(experimentName, config);
64
- return experiment;
65
- }
66
- logEvent(eventName, value, metadata) {
67
- const event = {
68
- eventName,
69
- value,
70
- metadata,
71
- user: this.user,
72
- time: Date.now(),
73
- };
74
- this.network.sendEvents([event]);
75
- }
76
- logExposure(experimentName, experiment) {
77
- var _a, _b, _c;
78
- const exposureEvent = {
79
- eventName: 'statsig::exposure',
80
- user: this.user,
81
- time: Date.now(),
82
- metadata: {
83
- config: experimentName,
84
- ruleID: (_a = experiment === null || experiment === void 0 ? void 0 : experiment.rule_id) !== null && _a !== void 0 ? _a : '',
85
- group: (_b = experiment === null || experiment === void 0 ? void 0 : experiment.group) !== null && _b !== void 0 ? _b : '',
86
- value: JSON.stringify((_c = experiment === null || experiment === void 0 ? void 0 : experiment.value) !== null && _c !== void 0 ? _c : {}),
87
- },
88
- };
89
- this.network.sendEvents([exposureEvent]);
90
- }
91
- }
92
- exports.StatsigClient = StatsigClient;
@@ -1,6 +0,0 @@
1
- export type StatsigUser = {
2
- userID?: string;
3
- email?: string;
4
- ip?: string;
5
- custom?: Record<string, string | number | boolean | Array<string>>;
6
- };
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });