unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
113 lines • 4.81 kB
JavaScript
import { CLIENT_METRICS_ADDED, FEATURE_ARCHIVED, FEATURE_CREATED, FEATURE_REVIVED, } from '../../events/index.js';
import { FeatureCompletedEvent, FeatureUncompletedEvent, } from '../../types/index.js';
import groupBy from 'lodash.groupby';
import { STAGE_ENTERED } from '../../metric-events.js';
export class FeatureLifecycleService {
constructor({ eventStore, featureLifecycleStore, environmentStore, featureEnvironmentStore, }, { eventService, }, { flagResolver, eventBus, getLogger, }) {
this.eventStore = eventStore;
this.featureLifecycleStore = featureLifecycleStore;
this.environmentStore = environmentStore;
this.featureEnvironmentStore = featureEnvironmentStore;
this.flagResolver = flagResolver;
this.eventBus = eventBus;
this.eventService = eventService;
this.logger = getLogger('feature-lifecycle/feature-lifecycle-service.ts');
}
listen() {
this.featureLifecycleStore.backfill();
this.eventStore.on(FEATURE_CREATED, async (event) => {
await this.featureInitialized(event.featureName);
});
this.eventBus.on(CLIENT_METRICS_ADDED, async (events) => {
if (events.length > 0) {
const groupedByEnvironment = groupBy(events, 'environment');
for (const [environment, metrics] of Object.entries(groupedByEnvironment)) {
const features = metrics.map((metric) => metric.featureName);
await this.featuresReceivedMetrics(features, environment);
}
}
});
this.eventStore.on(FEATURE_ARCHIVED, async (event) => {
await this.featureArchived(event.featureName);
});
this.eventStore.on(FEATURE_REVIVED, async (event) => {
await this.featureRevived(event.featureName);
});
}
async getFeatureLifecycle(feature) {
return this.featureLifecycleStore.get(feature);
}
async featureInitialized(feature) {
const result = await this.featureLifecycleStore.insert([
{ feature, stage: 'initial' },
]);
this.recordStagesEntered(result);
}
async stageReceivedMetrics(features, stage) {
const newlyEnteredStages = await this.featureLifecycleStore.insert(features.map((feature) => ({ feature, stage })));
this.recordStagesEntered(newlyEnteredStages);
}
recordStagesEntered(newlyEnteredStages) {
newlyEnteredStages.forEach(({ stage, feature }) => {
this.eventBus.emit(STAGE_ENTERED, { stage, feature });
});
}
async featuresReceivedMetrics(features, environment) {
try {
const env = await this.environmentStore.get(environment);
if (!env) {
return;
}
await this.stageReceivedMetrics(features, 'pre-live');
if (env.type === 'production') {
const featureEnv = await this.featureEnvironmentStore.getAllByFeatures(features, env.name);
const enabledFeatures = featureEnv
.filter((feature) => feature.enabled)
.map((feature) => feature.featureName);
await this.stageReceivedMetrics(enabledFeatures, 'live');
}
}
catch (e) {
this.logger.warn(`Error handling ${features.length} metrics in ${environment}`, e);
}
}
async featureCompleted(feature, projectId, status, auditUser) {
const result = await this.featureLifecycleStore.insert([
{
feature,
stage: 'completed',
status: status.status,
statusValue: status.statusValue,
},
]);
this.recordStagesEntered(result);
await this.eventService.storeEvent(new FeatureCompletedEvent({
project: projectId,
featureName: feature,
data: { ...status, kept: status.status === 'kept' },
auditUser,
}));
}
async featureUncompleted(feature, projectId, auditUser) {
await this.featureLifecycleStore.deleteStage({
feature,
stage: 'completed',
});
await this.eventService.storeEvent(new FeatureUncompletedEvent({
project: projectId,
featureName: feature,
auditUser,
}));
}
async featureArchived(feature) {
const result = await this.featureLifecycleStore.insert([
{ feature, stage: 'archived' },
]);
this.recordStagesEntered(result);
}
async featureRevived(feature) {
await this.featureLifecycleStore.delete(feature);
await this.featureInitialized(feature);
}
}
//# sourceMappingURL=feature-lifecycle-service.js.map