wolves-js-client 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EventLogger.d.ts +13 -0
- package/dist/EventLogger.js +67 -0
- package/dist/Experiment.d.ts +11 -0
- package/dist/Experiment.js +27 -0
- package/dist/Log.d.ts +15 -0
- package/dist/Log.js +43 -0
- package/dist/Network.d.ts +8 -0
- package/dist/Network.js +102 -0
- package/dist/StatsigClient.d.ts +15 -0
- package/dist/StatsigClient.js +92 -0
- package/dist/StatsigUser.d.ts +6 -0
- package/dist/StatsigUser.js +2 -0
- package/dist/Store.d.ts +18 -0
- package/dist/Store.js +25 -0
- package/dist/Types.d.ts +25 -0
- package/dist/Types.js +2 -0
- package/dist/WolvesClient.d.ts +25 -0
- package/dist/WolvesClient.js +143 -0
- package/dist/WolvesUser.d.ts +6 -0
- package/dist/WolvesUser.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/package.json +29 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Network } from './Network';
|
|
2
|
+
import { WolvesEvent } from './Types';
|
|
3
|
+
export declare class EventLogger {
|
|
4
|
+
private queue;
|
|
5
|
+
private flushIntervalId;
|
|
6
|
+
private network;
|
|
7
|
+
private maxQueueSize;
|
|
8
|
+
constructor(network: Network);
|
|
9
|
+
enqueue(event: WolvesEvent): void;
|
|
10
|
+
flush(): Promise<void>;
|
|
11
|
+
private start;
|
|
12
|
+
stop(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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.EventLogger = void 0;
|
|
13
|
+
const Log_1 = require("./Log");
|
|
14
|
+
const DEFAULT_QUEUE_SIZE = 100;
|
|
15
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 10000;
|
|
16
|
+
class EventLogger {
|
|
17
|
+
constructor(network) {
|
|
18
|
+
this.queue = [];
|
|
19
|
+
this.flushIntervalId = null;
|
|
20
|
+
this.maxQueueSize = DEFAULT_QUEUE_SIZE;
|
|
21
|
+
this.network = network;
|
|
22
|
+
this.start();
|
|
23
|
+
}
|
|
24
|
+
enqueue(event) {
|
|
25
|
+
this.queue.push(event);
|
|
26
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
27
|
+
this.flush();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
flush() {
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
if (this.queue.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const events = [...this.queue];
|
|
36
|
+
this.queue = [];
|
|
37
|
+
try {
|
|
38
|
+
yield this.network.sendEvents(events);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
Log_1.Log.error('Failed to flush events', e);
|
|
42
|
+
// Add failed events back to the front of the queue
|
|
43
|
+
this.queue = [...events, ...this.queue];
|
|
44
|
+
// Respect maxQueueSize to avoid memory issues
|
|
45
|
+
if (this.queue.length > this.maxQueueSize) {
|
|
46
|
+
this.queue = this.queue.slice(this.queue.length - this.maxQueueSize);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
start() {
|
|
52
|
+
if (this.flushIntervalId) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.flushIntervalId = setInterval(() => {
|
|
56
|
+
this.flush();
|
|
57
|
+
}, DEFAULT_FLUSH_INTERVAL_MS);
|
|
58
|
+
}
|
|
59
|
+
stop() {
|
|
60
|
+
if (this.flushIntervalId) {
|
|
61
|
+
clearInterval(this.flushIntervalId);
|
|
62
|
+
this.flushIntervalId = null;
|
|
63
|
+
}
|
|
64
|
+
this.flush();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.EventLogger = EventLogger;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class Experiment {
|
|
2
|
+
private name;
|
|
3
|
+
private value;
|
|
4
|
+
private ruleID;
|
|
5
|
+
private group;
|
|
6
|
+
constructor(name: string, value: Record<string, any>, ruleID: string, group: string);
|
|
7
|
+
get<T>(key: string, defaultValue: T): T;
|
|
8
|
+
getRuleID(): string;
|
|
9
|
+
getGroupName(): string;
|
|
10
|
+
getValue(): Record<string, any>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Experiment = void 0;
|
|
4
|
+
class Experiment {
|
|
5
|
+
constructor(name, value, ruleID, group) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.value = value;
|
|
8
|
+
this.ruleID = ruleID;
|
|
9
|
+
this.group = group;
|
|
10
|
+
}
|
|
11
|
+
get(key, defaultValue) {
|
|
12
|
+
if (this.value && key in this.value) {
|
|
13
|
+
return this.value[key];
|
|
14
|
+
}
|
|
15
|
+
return defaultValue;
|
|
16
|
+
}
|
|
17
|
+
getRuleID() {
|
|
18
|
+
return this.ruleID;
|
|
19
|
+
}
|
|
20
|
+
getGroupName() {
|
|
21
|
+
return this.group;
|
|
22
|
+
}
|
|
23
|
+
getValue() {
|
|
24
|
+
return this.value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.Experiment = Experiment;
|
package/dist/Log.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const LogLevel: {
|
|
2
|
+
readonly None: 0;
|
|
3
|
+
readonly Error: 1;
|
|
4
|
+
readonly Warn: 2;
|
|
5
|
+
readonly Info: 3;
|
|
6
|
+
readonly Debug: 4;
|
|
7
|
+
};
|
|
8
|
+
export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
|
|
9
|
+
export declare abstract class Log {
|
|
10
|
+
static level: LogLevel;
|
|
11
|
+
static info(...args: unknown[]): void;
|
|
12
|
+
static debug(...args: unknown[]): void;
|
|
13
|
+
static warn(...args: unknown[]): void;
|
|
14
|
+
static error(...args: unknown[]): void;
|
|
15
|
+
}
|
package/dist/Log.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Log = exports.LogLevel = void 0;
|
|
5
|
+
exports.LogLevel = {
|
|
6
|
+
None: 0,
|
|
7
|
+
Error: 1,
|
|
8
|
+
Warn: 2,
|
|
9
|
+
Info: 3,
|
|
10
|
+
Debug: 4,
|
|
11
|
+
};
|
|
12
|
+
const DEBUG = ' DEBUG ';
|
|
13
|
+
const _INFO = ' INFO ';
|
|
14
|
+
const _WARN = ' WARN ';
|
|
15
|
+
const ERROR = ' ERROR ';
|
|
16
|
+
function addTag(args) {
|
|
17
|
+
args.unshift('[Wolves]');
|
|
18
|
+
return args;
|
|
19
|
+
}
|
|
20
|
+
class Log {
|
|
21
|
+
static info(...args) {
|
|
22
|
+
if (Log.level >= exports.LogLevel.Info) {
|
|
23
|
+
console.info(_INFO, ...addTag(args));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static debug(...args) {
|
|
27
|
+
if (Log.level >= exports.LogLevel.Debug) {
|
|
28
|
+
console.debug(DEBUG, ...addTag(args));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
static warn(...args) {
|
|
32
|
+
if (Log.level >= exports.LogLevel.Warn) {
|
|
33
|
+
console.warn(_WARN, ...addTag(args));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
static error(...args) {
|
|
37
|
+
if (Log.level >= exports.LogLevel.Error) {
|
|
38
|
+
console.error(ERROR, ...addTag(args));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.Log = Log;
|
|
43
|
+
Log.level = exports.LogLevel.Warn;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { WolvesUser } from './WolvesUser';
|
|
2
|
+
export declare class Network {
|
|
3
|
+
private sdkKey;
|
|
4
|
+
private api;
|
|
5
|
+
constructor(sdkKey: string, api?: string);
|
|
6
|
+
fetchConfig(user: WolvesUser, sinceTime?: number, retries?: number, backoff?: number): Promise<any>;
|
|
7
|
+
sendEvents(events: any[], retries?: number, backoff?: number): Promise<void>;
|
|
8
|
+
}
|
package/dist/Network.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
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.Network = void 0;
|
|
13
|
+
const Log_1 = require("./Log");
|
|
14
|
+
const RETRYABLE_CODES = [408, 500, 502, 503, 504, 522, 524, 599];
|
|
15
|
+
class Network {
|
|
16
|
+
constructor(sdkKey, api = 'https://wolves-nova-dev.azurewebsites.net/api') {
|
|
17
|
+
this.sdkKey = sdkKey;
|
|
18
|
+
this.api = api;
|
|
19
|
+
}
|
|
20
|
+
fetchConfig(user_1, sinceTime_1) {
|
|
21
|
+
return __awaiter(this, arguments, void 0, function* (user, sinceTime, retries = 2, backoff = 1000) {
|
|
22
|
+
try {
|
|
23
|
+
const body = { user };
|
|
24
|
+
if (sinceTime) {
|
|
25
|
+
body.sinceTime = sinceTime;
|
|
26
|
+
}
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
|
|
29
|
+
const response = yield fetch(`${this.api}/events/initialize`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
'wolves-api-key': this.sdkKey,
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
signal: controller.signal,
|
|
37
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const status = response.status;
|
|
40
|
+
if (RETRYABLE_CODES.includes(status) && retries > 0) {
|
|
41
|
+
yield new Promise(resolve => setTimeout(resolve, backoff));
|
|
42
|
+
return this.fetchConfig(user, sinceTime, retries - 1, backoff * 2);
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
return yield response.json();
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
if (retries > 0) {
|
|
50
|
+
yield new Promise(resolve => setTimeout(resolve, backoff));
|
|
51
|
+
return this.fetchConfig(user, sinceTime, retries - 1, backoff * 2);
|
|
52
|
+
}
|
|
53
|
+
Log_1.Log.error('Failed to fetch config', e);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
sendEvents(events_1) {
|
|
59
|
+
return __awaiter(this, arguments, void 0, function* (events, retries = 3, backoff = 1000) {
|
|
60
|
+
const apiEvents = events.map(e => {
|
|
61
|
+
var _a, _b;
|
|
62
|
+
return ({
|
|
63
|
+
timestamp: new Date(e.time).toISOString(),
|
|
64
|
+
event: e.eventName,
|
|
65
|
+
user_id: (_b = (_a = e.user) === null || _a === void 0 ? void 0 : _a.userID) !== null && _b !== void 0 ? _b : '',
|
|
66
|
+
user_properties: e.user,
|
|
67
|
+
value: e.value,
|
|
68
|
+
metadata: e.metadata
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
|
|
74
|
+
const response = yield fetch(`${this.api}/events/ingest/batch`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
'wolves-api-key': this.sdkKey,
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ events: apiEvents }),
|
|
81
|
+
signal: controller.signal,
|
|
82
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const status = response.status;
|
|
85
|
+
if (RETRYABLE_CODES.includes(status) && retries > 0) {
|
|
86
|
+
yield new Promise(resolve => setTimeout(resolve, backoff));
|
|
87
|
+
return this.sendEvents(events, retries - 1, backoff * 2);
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Failed to log events: ${response.status} ${response.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
if (retries > 0) {
|
|
94
|
+
yield new Promise(resolve => setTimeout(resolve, backoff));
|
|
95
|
+
return this.sendEvents(events, retries - 1, backoff * 2);
|
|
96
|
+
}
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.Network = Network;
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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;
|
package/dist/Store.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ExperimentConfig = {
|
|
2
|
+
value: Record<string, any>;
|
|
3
|
+
experiment_id?: string;
|
|
4
|
+
group?: string;
|
|
5
|
+
};
|
|
6
|
+
export type InitializeResponse = {
|
|
7
|
+
dynamic_configs: Record<string, ExperimentConfig>;
|
|
8
|
+
has_updates: boolean;
|
|
9
|
+
time: number;
|
|
10
|
+
};
|
|
11
|
+
export declare class Store {
|
|
12
|
+
private values;
|
|
13
|
+
constructor();
|
|
14
|
+
setValues(values: InitializeResponse): void;
|
|
15
|
+
getExperiment(name: string): ExperimentConfig | null;
|
|
16
|
+
getValues(): InitializeResponse | null;
|
|
17
|
+
getLastUpdateTime(): number | undefined;
|
|
18
|
+
}
|
package/dist/Store.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Store = void 0;
|
|
4
|
+
class Store {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.values = null;
|
|
7
|
+
}
|
|
8
|
+
setValues(values) {
|
|
9
|
+
this.values = values;
|
|
10
|
+
}
|
|
11
|
+
getExperiment(name) {
|
|
12
|
+
if (!this.values || !this.values.dynamic_configs) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return this.values.dynamic_configs[name] || null;
|
|
16
|
+
}
|
|
17
|
+
getValues() {
|
|
18
|
+
return this.values;
|
|
19
|
+
}
|
|
20
|
+
getLastUpdateTime() {
|
|
21
|
+
var _a;
|
|
22
|
+
return (_a = this.values) === null || _a === void 0 ? void 0 : _a.time;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.Store = Store;
|
package/dist/Types.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WolvesUser } from './WolvesUser';
|
|
2
|
+
export type WolvesUpdateDetails = {
|
|
3
|
+
success: boolean;
|
|
4
|
+
errorMessage?: string;
|
|
5
|
+
};
|
|
6
|
+
export type WolvesEvent = {
|
|
7
|
+
eventName: string;
|
|
8
|
+
value?: string | number;
|
|
9
|
+
metadata?: Record<string, string>;
|
|
10
|
+
user?: WolvesUser;
|
|
11
|
+
time?: number;
|
|
12
|
+
};
|
|
13
|
+
export type ExperimentConfig = {
|
|
14
|
+
name: string;
|
|
15
|
+
value: Record<string, unknown>;
|
|
16
|
+
experiment_id?: string;
|
|
17
|
+
group?: string;
|
|
18
|
+
};
|
|
19
|
+
export type Experiment = {
|
|
20
|
+
name: string;
|
|
21
|
+
value: Record<string, unknown>;
|
|
22
|
+
experimentID: string;
|
|
23
|
+
groupName: string | null;
|
|
24
|
+
get: <T>(key: string, defaultValue: T) => T;
|
|
25
|
+
};
|
package/dist/Types.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WolvesUser } from './WolvesUser';
|
|
2
|
+
import { Experiment, WolvesUpdateDetails } from './Types';
|
|
3
|
+
export declare class WolvesClient {
|
|
4
|
+
private sdkKey;
|
|
5
|
+
private user;
|
|
6
|
+
private network;
|
|
7
|
+
private store;
|
|
8
|
+
private logger;
|
|
9
|
+
private initialized;
|
|
10
|
+
constructor(sdkKey: string, user: WolvesUser);
|
|
11
|
+
initializeAsync(): Promise<WolvesUpdateDetails>;
|
|
12
|
+
initializeSync(): WolvesUpdateDetails;
|
|
13
|
+
updateUserAsync(user: WolvesUser): Promise<WolvesUpdateDetails>;
|
|
14
|
+
updateUserSync(user: WolvesUser): WolvesUpdateDetails;
|
|
15
|
+
getExperiment(experimentName: string): Experiment;
|
|
16
|
+
/**
|
|
17
|
+
* Test-only method to manually trigger an exposure event with a specified group.
|
|
18
|
+
* This is useful for testing exposure logging without actual experiment assignment.
|
|
19
|
+
* The experiment ID is retrieved from the store config.
|
|
20
|
+
*/
|
|
21
|
+
getExperimentForTest(experimentName: string, groupName: string): Experiment;
|
|
22
|
+
logEvent(eventName: string, value?: string | number, metadata?: Record<string, string>): void;
|
|
23
|
+
shutdown(): void;
|
|
24
|
+
private logExposure;
|
|
25
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
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.WolvesClient = void 0;
|
|
13
|
+
const Network_1 = require("./Network");
|
|
14
|
+
const Store_1 = require("./Store");
|
|
15
|
+
const EventLogger_1 = require("./EventLogger");
|
|
16
|
+
class WolvesClient {
|
|
17
|
+
constructor(sdkKey, user) {
|
|
18
|
+
this.initialized = false;
|
|
19
|
+
this.sdkKey = sdkKey;
|
|
20
|
+
this.user = user;
|
|
21
|
+
this.network = new Network_1.Network(sdkKey);
|
|
22
|
+
this.store = new Store_1.Store();
|
|
23
|
+
this.logger = new EventLogger_1.EventLogger(this.network);
|
|
24
|
+
}
|
|
25
|
+
initializeAsync() {
|
|
26
|
+
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 };
|
|
32
|
+
}
|
|
33
|
+
return { success: false, errorMessage: 'Failed to fetch config' };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
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);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
this.initialized = true;
|
|
45
|
+
return { success: true };
|
|
46
|
+
}
|
|
47
|
+
updateUserAsync(user) {
|
|
48
|
+
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 };
|
|
55
|
+
}
|
|
56
|
+
return { success: false, errorMessage: 'Failed to fetch config' };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
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) => {
|
|
65
|
+
if (config) {
|
|
66
|
+
this.store.setValues(config);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return { success: true };
|
|
70
|
+
}
|
|
71
|
+
getExperiment(experimentName) {
|
|
72
|
+
var _a, _b, _c;
|
|
73
|
+
const config = this.store.getExperiment(experimentName);
|
|
74
|
+
const experiment = {
|
|
75
|
+
name: experimentName,
|
|
76
|
+
value: (_a = config === null || config === void 0 ? void 0 : config.value) !== null && _a !== void 0 ? _a : {},
|
|
77
|
+
experimentID: (_b = config === null || config === void 0 ? void 0 : config.experiment_id) !== null && _b !== void 0 ? _b : '',
|
|
78
|
+
groupName: (_c = config === null || config === void 0 ? void 0 : config.group) !== null && _c !== void 0 ? _c : null,
|
|
79
|
+
get: (key, defaultValue) => {
|
|
80
|
+
var _a;
|
|
81
|
+
const val = (_a = config === null || config === void 0 ? void 0 : config.value) === null || _a === void 0 ? void 0 : _a[key];
|
|
82
|
+
if (val === undefined || val === null) {
|
|
83
|
+
return defaultValue;
|
|
84
|
+
}
|
|
85
|
+
return val;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
// Log exposure
|
|
89
|
+
this.logExposure(experimentName, config);
|
|
90
|
+
return experiment;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Test-only method to manually trigger an exposure event with a specified group.
|
|
94
|
+
* This is useful for testing exposure logging without actual experiment assignment.
|
|
95
|
+
* The experiment ID is retrieved from the store config.
|
|
96
|
+
*/
|
|
97
|
+
getExperimentForTest(experimentName, groupName) {
|
|
98
|
+
var _a;
|
|
99
|
+
const config = this.store.getExperiment(experimentName);
|
|
100
|
+
const experimentId = (_a = config === null || config === void 0 ? void 0 : config.experiment_id) !== null && _a !== void 0 ? _a : '';
|
|
101
|
+
const experiment = {
|
|
102
|
+
name: experimentName,
|
|
103
|
+
value: {},
|
|
104
|
+
experimentID: experimentId,
|
|
105
|
+
groupName: groupName,
|
|
106
|
+
get: (_key, defaultValue) => {
|
|
107
|
+
return defaultValue;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
// Log exposure with the specified group and experiment ID from store
|
|
111
|
+
this.logExposure(experimentName, { group: groupName, experiment_id: experimentId, value: {} });
|
|
112
|
+
return experiment;
|
|
113
|
+
}
|
|
114
|
+
logEvent(eventName, value, metadata) {
|
|
115
|
+
const event = {
|
|
116
|
+
eventName,
|
|
117
|
+
value,
|
|
118
|
+
metadata,
|
|
119
|
+
user: this.user,
|
|
120
|
+
time: Date.now(),
|
|
121
|
+
};
|
|
122
|
+
this.logger.enqueue(event);
|
|
123
|
+
}
|
|
124
|
+
shutdown() {
|
|
125
|
+
this.logger.stop();
|
|
126
|
+
}
|
|
127
|
+
logExposure(experimentName, experiment) {
|
|
128
|
+
var _a, _b, _c;
|
|
129
|
+
const exposureEvent = {
|
|
130
|
+
eventName: 'exposure',
|
|
131
|
+
user: this.user,
|
|
132
|
+
time: Date.now(),
|
|
133
|
+
metadata: {
|
|
134
|
+
experimentName: experimentName,
|
|
135
|
+
experimentID: (_a = experiment === null || experiment === void 0 ? void 0 : experiment.experiment_id) !== null && _a !== void 0 ? _a : '',
|
|
136
|
+
group: (_b = experiment === null || experiment === void 0 ? void 0 : experiment.group) !== null && _b !== void 0 ? _b : '',
|
|
137
|
+
value: JSON.stringify((_c = experiment === null || experiment === void 0 ? void 0 : experiment.value) !== null && _c !== void 0 ? _c : {}),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
this.logger.enqueue(exposureEvent);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.WolvesClient = WolvesClient;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogLevel = exports.Log = exports.WolvesClient = void 0;
|
|
4
|
+
var WolvesClient_1 = require("./WolvesClient");
|
|
5
|
+
Object.defineProperty(exports, "WolvesClient", { enumerable: true, get: function () { return WolvesClient_1.WolvesClient; } });
|
|
6
|
+
var Log_1 = require("./Log");
|
|
7
|
+
Object.defineProperty(exports, "Log", { enumerable: true, get: function () { return Log_1.Log; } });
|
|
8
|
+
Object.defineProperty(exports, "LogLevel", { enumerable: true, get: function () { return Log_1.LogLevel; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wolves-js-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Wolves JavaScript Client SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"wolves",
|
|
12
|
+
"ab-testing",
|
|
13
|
+
"experiment",
|
|
14
|
+
"sdk"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.0.0",
|
|
25
|
+
"jest": "^29.0.0",
|
|
26
|
+
"ts-jest": "^29.0.0",
|
|
27
|
+
"@types/jest": "^29.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|