unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
137 lines • 6.77 kB
JavaScript
import { DefaultStrategyUpdatedEvent, ProjectEnvironmentAdded, ProjectEnvironmentRemoved, SYSTEM_USER_AUDIT, } from '../../types/index.js';
import { BadDataError, UNIQUE_CONSTRAINT_VIOLATION, } from '../../error/index.js';
import NameExistsError from '../../error/name-exists-error.js';
import { sortOrderSchema } from '../../services/sort-order-schema.js';
import NotFoundError from '../../error/notfound-error.js';
export default class EnvironmentService {
constructor({ environmentStore, featureStrategiesStore, featureEnvironmentStore, projectStore, }, { getLogger, flagResolver, }, eventService) {
this.logger = getLogger('services/environment-service.ts');
this.environmentStore = environmentStore;
this.featureStrategiesStore = featureStrategiesStore;
this.featureEnvironmentStore = featureEnvironmentStore;
this.projectStore = projectStore;
this.eventService = eventService;
this.flagResolver = flagResolver;
}
async getAll() {
return this.environmentStore.getAllWithCounts();
}
async get(name) {
const env = await this.environmentStore.get(name);
if (env === undefined) {
throw new NotFoundError(`Could not find environment with name ${name}`);
}
return env;
}
async exists(name) {
return this.environmentStore.exists(name);
}
async getProjectEnvironments(projectId) {
// This function produces an object for every environment, in that object is a boolean
// describing whether that environment is enabled - aka not deprecated
const environments = await this.projectStore.getEnvironmentsForProject(projectId);
const environmentsOnProject = new Set(environments.map((env) => env.environment));
const allEnvironments = await this.environmentStore.getProjectEnvironments(projectId);
return allEnvironments.map((env) => {
return {
...env,
visible: environmentsOnProject.has(env.name),
};
});
}
async updateSortOrder(sortOrder) {
await sortOrderSchema.validateAsync(sortOrder);
await Promise.all(Object.keys(sortOrder).map((key) => {
const value = sortOrder[key];
return this.environmentStore.updateSortOrder(key, value);
}));
}
async toggleEnvironment(name, value) {
const exists = await this.environmentStore.exists(name);
if (exists) {
return this.environmentStore.toggle(name, value);
}
throw new NotFoundError(`Could not find environment ${name}`);
}
async addEnvironmentToProject(environment, projectId, auditUser) {
try {
await this.featureEnvironmentStore.connectProject(environment, projectId);
await this.featureEnvironmentStore.connectFeatures(environment, projectId);
await this.eventService.storeEvent(new ProjectEnvironmentAdded({
project: projectId,
environment,
auditUser,
}));
}
catch (e) {
if (e.code === UNIQUE_CONSTRAINT_VIOLATION) {
throw new NameExistsError(`${projectId} already has the environment ${environment} enabled`);
}
throw e;
}
}
async updateDefaultStrategy(environment, projectId, strategy, auditUser) {
if (strategy.name !== 'flexibleRollout') {
throw new BadDataError('Only "flexibleRollout" strategy can be used as a default strategy for an environment');
}
const previousDefaultStrategy = await this.projectStore.getDefaultStrategy(projectId, environment);
const defaultStrategy = await this.projectStore.updateDefaultStrategy(projectId, environment, strategy);
await this.eventService.storeEvent(new DefaultStrategyUpdatedEvent({
project: projectId,
environment,
preData: previousDefaultStrategy,
data: defaultStrategy,
auditUser,
}));
return defaultStrategy;
}
async overrideEnabledProjects(environmentNamesToEnable) {
if (environmentNamesToEnable.length === 0) {
return Promise.resolve();
}
const allEnvironments = await this.environmentStore.getAll();
const existingEnvironmentsToEnable = allEnvironments.filter((env) => environmentNamesToEnable.includes(env.name));
if (existingEnvironmentsToEnable.length !==
environmentNamesToEnable.length) {
this.logger.warn("Found environment enabled overrides but some of the specified environments don't exist, no overrides will be executed");
return Promise.resolve();
}
const environmentsNotAlreadyEnabled = existingEnvironmentsToEnable.filter((env) => !env.enabled);
const environmentsToDisable = allEnvironments.filter((env) => {
return !environmentNamesToEnable.includes(env.name) && env.enabled;
});
await this.environmentStore.disable(environmentsToDisable);
await this.environmentStore.enable(environmentsNotAlreadyEnabled);
await this.remapProjectsLinks(environmentsToDisable, environmentsNotAlreadyEnabled);
}
async remapProjectsLinks(toDisable, toEnable) {
const projectLinks = await this.projectStore.getProjectLinksForEnvironments(toDisable.map((env) => env.name));
const unlinkTasks = projectLinks.map((link) => {
return this.forceRemoveEnvironmentFromProject(link.environmentName, link.projectId);
});
await Promise.all(unlinkTasks.flat());
const uniqueProjects = [
...new Set(projectLinks.map((link) => link.projectId)),
];
const linkTasks = uniqueProjects.flatMap((project) => {
return toEnable.map((enabledEnv) => {
return this.addEnvironmentToProject(enabledEnv.name, project, SYSTEM_USER_AUDIT);
});
});
await Promise.all(linkTasks);
}
async forceRemoveEnvironmentFromProject(environment, projectId) {
await this.featureEnvironmentStore.disconnectFeatures(environment, projectId);
await this.featureEnvironmentStore.disconnectProject(environment, projectId);
}
async removeEnvironmentFromProject(environment, projectId, auditUser) {
const _projectEnvs = await this.projectStore.getEnvironmentsForProject(projectId);
await this.forceRemoveEnvironmentFromProject(environment, projectId);
await this.eventService.storeEvent(new ProjectEnvironmentRemoved({
project: projectId,
environment,
auditUser,
}));
}
}
//# sourceMappingURL=environment-service.js.map