UNPKG

featurehub-repository

Version:

Core package of API that exposes FeatureHub feature flags, values and configuration to client applications written in Typescript or Javascript.

287 lines 11.1 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { FeatureStateBaseHolder } from './feature_state_holders'; import { FeatureStateTypeTransformer, FeatureValueType, SSEResultState } from './models'; import { ApplyFeature } from './strategy_matcher'; import { fhLog } from './feature_hub_config'; export var Readyness; (function (Readyness) { Readyness["NotReady"] = "NotReady"; Readyness["Ready"] = "Ready"; Readyness["Failed"] = "Failed"; })(Readyness || (Readyness = {})); export class ClientFeatureRepository { constructor(applyFeature) { this.features = new Map(); this.analyticsCollectors = new Array(); this.readynessState = Readyness.NotReady; this.readynessListeners = []; this._catchAndReleaseMode = false; this._catchReleaseStates = new Map(); this._newFeatureStateAvailableListeners = []; this._matchers = []; this._applyFeature = applyFeature || new ApplyFeature(); } apply(strategies, key, featureValueId, context) { return this._applyFeature.apply(strategies, key, featureValueId, context); } get readyness() { return this.readynessState; } notify(state, data) { if (state !== null && state !== undefined) { switch (state) { case SSEResultState.Ack: break; case SSEResultState.Bye: this.readynessState = Readyness.NotReady; if (!this._catchAndReleaseMode) { this.broadcastReadynessState(); } break; case SSEResultState.DeleteFeature: this.deleteFeature(FeatureStateTypeTransformer.fromJson(data)); break; case SSEResultState.Failure: this.readynessState = Readyness.Failed; if (!this._catchAndReleaseMode) { this.broadcastReadynessState(); } break; case SSEResultState.Feature: const fs = FeatureStateTypeTransformer.fromJson(data); if (this._catchAndReleaseMode) { this._catchUpdatedFeatures([fs]); } else { if (this.featureUpdate(fs)) { this.triggerNewStateAvailable(); } } break; case SSEResultState.Features: const features = (data instanceof Array) ? data : data.map((f) => FeatureStateTypeTransformer.fromJson(f)); if (this.hasReceivedInitialState && this._catchAndReleaseMode) { this._catchUpdatedFeatures(features); } else { let updated = false; features.forEach((f) => updated = this.featureUpdate(f) || updated); this.readynessState = Readyness.Ready; if (!this.hasReceivedInitialState) { this.hasReceivedInitialState = true; this.broadcastReadynessState(); } else if (updated) { this.triggerNewStateAvailable(); } } break; default: break; } } } addValueInterceptor(matcher) { this._matchers.push(matcher); matcher.repository(this); } valueInterceptorMatched(key) { for (let matcher of this._matchers) { const m = matcher.matched(key); if (m === null || m === void 0 ? void 0 : m.value) { return m; } } return null; } addPostLoadNewFeatureStateAvailableListener(listener) { this._newFeatureStateAvailableListeners.push(listener); if (this._catchReleaseStates.size > 0) { listener(this); } } addReadynessListener(listener) { this.readynessListeners.push(listener); listener(this.readynessState); } notReady() { this.readynessState = Readyness.NotReady; this.broadcastReadynessState(); } broadcastReadynessState() { return __awaiter(this, void 0, void 0, function* () { this.readynessListeners.forEach((l) => l(this.readynessState)); }); } addAnalyticCollector(collector) { this.analyticsCollectors.push(collector); } simpleFeatures() { const vals = new Map(); this.features.forEach((value, key) => { if (value.getKey()) { let val; switch (value.getType()) { case FeatureValueType.Boolean: val = value.getBoolean() ? 'true' : 'false'; break; case FeatureValueType.String: val = value.getString(); break; case FeatureValueType.Number: val = value.getNumber(); break; case FeatureValueType.Json: val = value.getRawJson(); break; default: val = undefined; } vals.set(key, val === undefined ? val : val.toString()); } }); return vals; } logAnalyticsEvent(action, other, ctx) { return __awaiter(this, void 0, void 0, function* () { const featureStateAtCurrentTime = []; for (let fs of this.features.values()) { if (fs.isSet()) { const fsVal = ctx == null ? fs : fs.withContext(ctx); featureStateAtCurrentTime.push(fsVal.analyticsCopy()); } } this.analyticsCollectors.forEach((ac) => ac.logEvent(action, other, featureStateAtCurrentTime)); }); } hasFeature(key) { return this.features.get(key); } feature(key) { let holder = this.features.get(key); if (holder === undefined) { holder = new FeatureStateBaseHolder(this, key); this.features.set(key, holder); } return holder; } getFeatureState(key) { return this.feature(key); } get catchAndReleaseMode() { return this._catchAndReleaseMode; } set catchAndReleaseMode(value) { if (this._catchAndReleaseMode !== value && value === false) { this.release(true); } this._catchAndReleaseMode = value; } release(disableCatchAndRelease) { return __awaiter(this, void 0, void 0, function* () { while (this._catchReleaseStates.size > 0) { const states = [...this._catchReleaseStates.values()]; this._catchReleaseStates.clear(); states.forEach((fs) => this.featureUpdate(fs)); } if (disableCatchAndRelease === true) { this._catchAndReleaseMode = false; } }); } getFlag(key) { return this.feature(key).getFlag(); } getString(key) { return this.feature(key).getString(); } getJson(key) { return this.feature(key).getRawJson(); } getNumber(key) { return this.feature(key).getNumber(); } isSet(key) { return this.feature(key).isSet(); } _catchUpdatedFeatures(features) { let updatedValues = false; if (features && features.length > 0) { features.forEach((f) => { const existingFeature = this.features.get(f.key); if (existingFeature === null || (existingFeature.getKey() && f.version > existingFeature.getFeatureState().version)) { const fs = this._catchReleaseStates.get(f.id); if (fs == null) { this._catchReleaseStates.set(f.id, f); updatedValues = true; } else { if (fs.version === undefined || (f.version !== undefined && f.version > fs.version)) { this._catchReleaseStates.set(f.id, f); updatedValues = true; } } } }); } if (updatedValues) { this.triggerNewStateAvailable(); } } triggerNewStateAvailable() { return __awaiter(this, void 0, void 0, function* () { if (this.hasReceivedInitialState && this._newFeatureStateAvailableListeners.length > 0) { if (!this._catchAndReleaseMode || (this._catchReleaseStates.size > 0)) { this._newFeatureStateAvailableListeners.forEach((l) => { try { l(this); } catch (e) { fhLog.log('failed', e); } }); } } else { } }); } featureUpdate(fs) { if (fs === undefined || fs.key === undefined) { return false; } let holder = this.features.get(fs.key); if (holder === undefined) { const newFeature = new FeatureStateBaseHolder(this, fs.key, holder); this.features.set(fs.key, newFeature); holder = newFeature; } else if (holder.getFeatureState() !== undefined) { if (fs.version < holder.getFeatureState().version) { return false; } else if (fs.version === holder.getFeatureState().version && fs.value === holder.getFeatureState().value) { return false; } } return holder.setFeatureState(fs); } deleteFeature(featureState) { featureState.value = undefined; let holder = this.features.get(featureState.key); if (holder) { holder.setFeatureState(featureState); } } } //# sourceMappingURL=client_feature_repository.js.map