@featurevisor/sdk 0.19.0 → 0.20.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.
@@ -1,306 +0,0 @@
1
- import { DatafileContent, Attributes, StickyFeatures, InitialFeatures } from "@featurevisor/types";
2
- import { FeaturevisorSDK, ConfigureBucketValue, ActivationCallback } from "./client";
3
- import { createLogger, Logger } from "./logger";
4
- import { Emitter } from "./emitter";
5
- import { Statuses } from "./statuses";
6
-
7
- export type ReadyCallback = () => void;
8
-
9
- export interface InstanceOptions {
10
- // from SdkOptions
11
- datafile?: DatafileContent | string; // optional here, but not in SdkOptions
12
- onActivation?: ActivationCallback;
13
- configureBucketValue?: ConfigureBucketValue;
14
-
15
- // additions
16
- datafileUrl?: string;
17
- onReady?: ReadyCallback;
18
- handleDatafileFetch?: (datafileUrl: string) => Promise<DatafileContent>;
19
- logger?: Logger;
20
- interceptAttributes?: (attributes: Attributes) => Attributes;
21
- refreshInterval?: number; // seconds
22
- onRefresh?: () => void;
23
- onUpdate?: () => void;
24
- stickyFeatures?: StickyFeatures;
25
- initialFeatures?: InitialFeatures;
26
- }
27
-
28
- export type Event = "ready" | "refresh" | "update" | "activation";
29
-
30
- // @TODO: consider renaming it to FeaturevisorSDK in next breaking semver
31
- export interface FeaturevisorInstance {
32
- /**
33
- * From FeaturevisorSDK
34
- */
35
-
36
- // variation
37
- getVariation: FeaturevisorSDK["getVariation"];
38
- getVariationBoolean: FeaturevisorSDK["getVariationBoolean"];
39
- getVariationInteger: FeaturevisorSDK["getVariationInteger"];
40
- getVariationDouble: FeaturevisorSDK["getVariationDouble"];
41
- getVariationString: FeaturevisorSDK["getVariationString"];
42
-
43
- // activate
44
- activate: FeaturevisorSDK["activate"];
45
- activateBoolean: FeaturevisorSDK["activateBoolean"];
46
- activateInteger: FeaturevisorSDK["activateInteger"];
47
- activateDouble: FeaturevisorSDK["activateDouble"];
48
- activateString: FeaturevisorSDK["activateString"];
49
-
50
- // variable
51
- getVariable: FeaturevisorSDK["getVariable"];
52
- getVariableBoolean: FeaturevisorSDK["getVariableBoolean"];
53
- getVariableInteger: FeaturevisorSDK["getVariableInteger"];
54
- getVariableDouble: FeaturevisorSDK["getVariableDouble"];
55
- getVariableString: FeaturevisorSDK["getVariableString"];
56
- getVariableArray: FeaturevisorSDK["getVariableArray"];
57
- getVariableObject: FeaturevisorSDK["getVariableObject"];
58
-
59
- /**
60
- * Additions
61
- */
62
- setLogLevels: Logger["setLevels"];
63
- setStickyFeatures: FeaturevisorSDK["setStickyFeatures"];
64
-
65
- refresh: () => void;
66
- startRefreshing: () => void;
67
- stopRefreshing: () => void;
68
-
69
- addListener: Emitter["addListener"];
70
- on: Emitter["addListener"];
71
- removeListener: Emitter["removeListener"];
72
- off: Emitter["removeListener"];
73
- removeAllListeners: Emitter["removeAllListeners"];
74
-
75
- isReady: () => boolean;
76
- }
77
-
78
- function fetchDatafileContent(datafileUrl, options: InstanceOptions): Promise<DatafileContent> {
79
- if (options.handleDatafileFetch) {
80
- return options.handleDatafileFetch(datafileUrl);
81
- }
82
-
83
- return fetch(datafileUrl).then((res) => res.json());
84
- }
85
-
86
- interface Listeners {
87
- [key: string]: Function[];
88
- }
89
-
90
- function getInstanceFromSdk(
91
- sdk: FeaturevisorSDK,
92
- options: InstanceOptions,
93
- logger: Logger,
94
- emitter: Emitter,
95
- statuses: Statuses,
96
- ): FeaturevisorInstance {
97
- let intervalId;
98
-
99
- const on = emitter.addListener.bind(emitter);
100
- const off = emitter.removeListener.bind(emitter);
101
-
102
- const instance: FeaturevisorInstance = {
103
- // variation
104
- getVariation: sdk.getVariation.bind(sdk),
105
- getVariationBoolean: sdk.getVariationBoolean.bind(sdk),
106
- getVariationInteger: sdk.getVariationInteger.bind(sdk),
107
- getVariationDouble: sdk.getVariationDouble.bind(sdk),
108
- getVariationString: sdk.getVariationString.bind(sdk),
109
-
110
- // activate
111
- activate: sdk.activate.bind(sdk),
112
- activateBoolean: sdk.activateBoolean.bind(sdk),
113
- activateInteger: sdk.activateInteger.bind(sdk),
114
- activateDouble: sdk.activateDouble.bind(sdk),
115
- activateString: sdk.activateString.bind(sdk),
116
-
117
- // variable
118
- getVariable: sdk.getVariable.bind(sdk),
119
- getVariableBoolean: sdk.getVariableBoolean.bind(sdk),
120
- getVariableInteger: sdk.getVariableInteger.bind(sdk),
121
- getVariableDouble: sdk.getVariableDouble.bind(sdk),
122
- getVariableString: sdk.getVariableString.bind(sdk),
123
- getVariableArray: sdk.getVariableArray.bind(sdk),
124
- getVariableObject: sdk.getVariableObject.bind(sdk),
125
-
126
- // additions
127
- setLogLevels: logger.setLevels.bind(logger),
128
- setStickyFeatures: sdk.setStickyFeatures.bind(sdk),
129
-
130
- // emitter
131
- on: on,
132
- addListener: on,
133
- off: off,
134
- removeListener: off,
135
- removeAllListeners: emitter.removeAllListeners.bind(emitter),
136
-
137
- // refresh
138
- refresh() {
139
- logger.debug("refreshing datafile");
140
-
141
- if (statuses.refreshInProgress) {
142
- return logger.warn("refresh in progress, skipping");
143
- }
144
-
145
- if (!options.datafileUrl) {
146
- return logger.error("cannot refresh since `datafileUrl` is not provided");
147
- }
148
-
149
- statuses.refreshInProgress = true;
150
-
151
- fetchDatafileContent(options.datafileUrl, options)
152
- .then((datafile) => {
153
- const currentRevision = sdk.getRevision();
154
- const newRevision = datafile.revision;
155
- const isNotSameRevision = currentRevision !== newRevision;
156
-
157
- sdk.setDatafile(datafile);
158
- logger.info("refreshed datafile");
159
-
160
- emitter.emit("refresh");
161
-
162
- if (isNotSameRevision) {
163
- emitter.emit("update");
164
- }
165
-
166
- statuses.refreshInProgress = false;
167
- })
168
- .catch((e) => {
169
- logger.error("failed to refresh datafile", { error: e });
170
- statuses.refreshInProgress = false;
171
- });
172
- },
173
-
174
- startRefreshing() {
175
- if (!options.datafileUrl) {
176
- return logger.error("cannot start refreshing since `datafileUrl` is not provided");
177
- }
178
-
179
- if (intervalId) {
180
- return logger.warn("refreshing has already started");
181
- }
182
-
183
- if (!options.refreshInterval) {
184
- return logger.warn("no `refreshInterval` option provided");
185
- }
186
-
187
- intervalId = setInterval(() => {
188
- instance.refresh();
189
- }, options.refreshInterval * 1000);
190
- },
191
-
192
- stopRefreshing() {
193
- if (!intervalId) {
194
- return logger.warn("refreshing has not started yet");
195
- }
196
-
197
- clearInterval(intervalId);
198
- },
199
-
200
- isReady() {
201
- return statuses.ready;
202
- },
203
- };
204
-
205
- if (options.datafileUrl && options.refreshInterval) {
206
- instance.startRefreshing();
207
- }
208
-
209
- return instance;
210
- }
211
-
212
- const emptyDatafile: DatafileContent = {
213
- schemaVersion: "1",
214
- revision: "unknown",
215
- attributes: [],
216
- segments: [],
217
- features: [],
218
- };
219
-
220
- export function createInstance(options: InstanceOptions) {
221
- if (!options.datafile && !options.datafileUrl) {
222
- throw new Error(
223
- "Featurevisor SDK instance cannot be created without both `datafile` and `datafileUrl` options",
224
- );
225
- }
226
-
227
- const logger = options.logger || createLogger();
228
- const emitter = new Emitter();
229
- const statuses: Statuses = {
230
- ready: false,
231
- refreshInProgress: false,
232
- };
233
-
234
- if (!options.datafileUrl && options.refreshInterval) {
235
- logger.warn("refreshing datafile requires `datafileUrl` option");
236
- }
237
-
238
- if (options.onReady) {
239
- emitter.addListener("ready", options.onReady);
240
- }
241
-
242
- if (options.onActivation) {
243
- emitter.addListener("activation", options.onActivation);
244
- }
245
-
246
- if (options.onRefresh) {
247
- emitter.addListener("refresh", options.onRefresh);
248
- }
249
-
250
- if (options.onUpdate) {
251
- emitter.addListener("update", options.onUpdate);
252
- }
253
-
254
- // datafile content is already provided
255
- if (options.datafile) {
256
- const sdk = new FeaturevisorSDK({
257
- datafile: options.datafile,
258
- onActivation: options.onActivation,
259
- configureBucketValue: options.configureBucketValue,
260
- logger,
261
- emitter,
262
- interceptAttributes: options.interceptAttributes,
263
- stickyFeatures: options.stickyFeatures,
264
- initialFeatures: options.initialFeatures,
265
- statuses,
266
- fromInstance: true,
267
- });
268
-
269
- statuses.ready = true;
270
- setTimeout(function () {
271
- emitter.emit("ready");
272
- }, 0);
273
-
274
- return getInstanceFromSdk(sdk, options, logger, emitter, statuses);
275
- }
276
-
277
- // datafile has to be fetched
278
- const sdk = new FeaturevisorSDK({
279
- datafile: emptyDatafile,
280
- onActivation: options.onActivation,
281
- configureBucketValue: options.configureBucketValue,
282
- logger,
283
- emitter,
284
- interceptAttributes: options.interceptAttributes,
285
- stickyFeatures: options.stickyFeatures,
286
- initialFeatures: options.initialFeatures,
287
- statuses,
288
- fromInstance: true,
289
- });
290
-
291
- if (options.datafileUrl) {
292
- fetchDatafileContent(options.datafileUrl, options)
293
- .then((datafile) => {
294
- sdk.setDatafile(datafile);
295
-
296
- statuses.ready = true;
297
- emitter.emit("ready");
298
- })
299
- .catch((e) => {
300
- logger.error("failed to fetch datafile:");
301
- console.error(e);
302
- });
303
- }
304
-
305
- return getInstanceFromSdk(sdk, options, logger, emitter, statuses);
306
- }
package/src/statuses.ts DELETED
@@ -1,4 +0,0 @@
1
- export interface Statuses {
2
- ready: boolean;
3
- refreshInProgress: boolean;
4
- }