UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

204 lines 6.79 kB
import metricsHelper from '../util/metrics-helper.js'; import { DB_TIME } from '../metric-events.js'; import NotFoundError from '../error/notfound-error.js'; const COLUMNS = ['feature_name', 'tag_type', 'tag_value']; const TABLE = 'feature_tag'; class FeatureTagStore { constructor(db, eventBus, getLogger) { this.db = db; this.logger = getLogger('feature-tag-store.ts'); this.timer = (action) => metricsHelper.wrapTimer(eventBus, DB_TIME, { store: 'feature-tag-toggle', action, }); } async delete({ featureName, tagType, tagValue, }) { await this.db(TABLE) .where({ feature_name: featureName, tag_type: tagType, tag_value: tagValue, }) .del(); } destroy() { } async exists({ featureName, tagType, tagValue, }) { const result = await this.db.raw(`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE feature_name = ? AND tag_type = ? AND tag_value = ?) AS present`, [featureName, tagType, tagValue]); const { present } = result.rows[0]; return present; } async get({ featureName, tagType, tagValue, }) { const row = await this.db(TABLE) .where({ feature_name: featureName, tag_type: tagType, tag_value: tagValue, }) .first(); return { featureName: row.feature_name, tagType: row.tag_type, tagValue: row.tag_value, createdByUserId: row.created_by_user_id, }; } async getAll() { const rows = await this.db(TABLE).select(COLUMNS); return rows.map((row) => ({ featureName: row.feature_name, tagType: row.tag_type, tagValue: row.tag_value, createdByUserId: row.created_by_user_id, })); } async getAllTagsForFeature(featureName) { const stopTimer = this.timer('getAllForFeature'); if (await this.featureExists(featureName)) { const rows = await this.db .select([...COLUMNS, 'tag_types.color as color']) .from(TABLE) .leftJoin('tag_types', 'tag_types.name', 'feature_tag.tag_type') .where({ feature_name: featureName }); stopTimer(); return rows.map((row) => ({ type: row.tag_type, value: row.tag_value, color: row.color, })); } else { throw new NotFoundError(`Could not find feature with name ${featureName}`); } } async getAllFeaturesForTag(tagValue) { const rows = await this.db .select('feature_name') .from(TABLE) .where({ tag_value: tagValue }); return rows.map(({ feature_name }) => feature_name); } async featureExists(featureName) { const result = await this.db.raw('SELECT EXISTS (SELECT 1 FROM features WHERE name = ?) AS present', [featureName]); const { present } = result.rows[0]; return present; } async getAllByFeatures(features) { const query = this.db .select(COLUMNS) .from(TABLE) .whereIn('feature_name', features) .orderBy('feature_name', 'asc'); const rows = await query; return rows.map((row) => ({ featureName: row.feature_name, tagType: row.tag_type, tagValue: row.tag_value, createdByUserId: row.created_by_user_id, })); } async tagFeature(featureName, tag, createdByUserId) { const stopTimer = this.timer('tagFeature'); await this.db(TABLE) .insert(this.featureAndTagToRow(featureName, tag, createdByUserId)) .onConflict(COLUMNS) .merge(); stopTimer(); return tag; } async untagFeatures(featureTags) { const stopTimer = this.timer('untagFeatures'); try { await this.db(TABLE) .whereIn(COLUMNS, featureTags.map(this.featureTagArray)) .delete(); } catch (err) { this.logger.error(err); } stopTimer(); } /** * Only gets tags for active feature flags. */ async getAllFeatureTags() { const rows = await this.db(TABLE) .select(COLUMNS) .whereIn('feature_name', this.db('features').where({ archived: false }).select(['name'])); return rows.map((row) => ({ featureName: row.feature_name, tagType: row.tag_type, tagValue: row.tag_value, createdByUserId: row.created_by_user_id, })); } async deleteAll() { const stopTimer = this.timer('deleteAll'); await this.db(TABLE).del(); stopTimer(); } async tagFeatures(featureTags) { if (featureTags.length !== 0) { const rows = await this.db(TABLE) .insert(featureTags.map(this.featureTagToRow)) .returning(COLUMNS) .onConflict(COLUMNS) .ignore(); if (rows) { return rows.map(this.rowToFeatureAndTag); } } return []; } async untagFeature(featureName, tag) { const stopTimer = this.timer('untagFeature'); try { await this.db(TABLE) .where({ feature_name: featureName, tag_type: tag.type, tag_value: tag.value, }) .delete(); } catch (err) { this.logger.error(err); } stopTimer(); } featureTagRowToTag(row) { return { value: row.tag_value, type: row.tag_type, }; } rowToFeatureAndTag(row) { return { featureName: row.feature_name, tag: { type: row.tag_type, value: row.tag_value, }, }; } featureTagToRow({ featureName, tagType, tagValue, createdByUserId, }) { return { feature_name: featureName, tag_type: tagType, tag_value: tagValue, created_by_user_id: createdByUserId, }; } featureTagArray({ featureName, tagType, tagValue }) { return [featureName, tagType, tagValue]; } featureAndTagToRow(featureName, { type, value }, createdByUserId) { return { feature_name: featureName, tag_type: type, tag_value: value, created_by_user_id: createdByUserId, }; } } export default FeatureTagStore; //# sourceMappingURL=feature-tag-store.js.map