unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
95 lines • 3.44 kB
JavaScript
const TABLE = 'unknown_flags';
const TABLE_EVENTS = 'events';
const MAX_INSERT_BATCH_SIZE = 100;
export class UnknownFlagsStore {
constructor(db, getLogger) {
this.db = db;
this.logger = getLogger('unknown-flags-store.ts');
}
async insert(flags) {
if (!flags.length)
return;
const rows = flags.map(({ name, appName, lastSeenAt, environment }) => ({
name,
app_name: appName,
seen_at: lastSeenAt,
environment,
}));
for (let i = 0; i < rows.length; i += MAX_INSERT_BATCH_SIZE) {
const chunk = rows.slice(i, i + MAX_INSERT_BATCH_SIZE);
try {
await this.db(TABLE)
.insert(chunk)
.onConflict(['name', 'app_name', 'environment'])
.merge(['seen_at']);
}
catch (error) {
this.logger.debug(`unknown_flags: batch ${i / MAX_INSERT_BATCH_SIZE + 1} failed and was skipped.`, error);
}
}
}
async getAll({ limit, orderBy } = {}) {
const base = this.db
.with('base', (qb) => qb
.from(`${TABLE} as uf`)
.leftJoin('features as f', 'f.name', 'uf.name')
.whereNull('f.name')
.select('uf.name', 'uf.app_name', 'uf.environment')
.max({ seen_at: 'uf.seen_at' })
.groupBy('uf.name', 'uf.app_name', 'uf.environment'))
.select('b.name', this.db.raw('MAX(b.seen_at) as last_seen_at'), this.db.raw(`(SELECT MAX(e.created_at) FROM ${TABLE_EVENTS} e WHERE e.feature_name = b.name) as last_event_at`), this.db.raw(`
jsonb_object_agg(
b.app_name,
(
SELECT jsonb_object_agg(env_row.environment, env_row.seen_at)
FROM (
SELECT environment, MAX(seen_at) AS seen_at
FROM base
WHERE name = b.name AND app_name = b.app_name
GROUP BY environment
) env_row
)
) as reports
`))
.from('base as b')
.groupBy('b.name');
let q = base;
if (orderBy)
q = q.orderBy(orderBy);
if (limit)
q = q.limit(limit);
const rows = await q;
return rows.map((r) => {
const reportsObj = r.reports ?? {};
const reports = Object.entries(reportsObj).map(([appName, envs]) => ({
appName,
environments: Object.entries(envs).map(([environment, seenAt]) => ({
environment,
seenAt: new Date(seenAt),
})),
}));
return {
name: r.name,
lastSeenAt: r.last_seen_at,
lastEventAt: r.last_event_at,
reports,
};
});
}
async clear(hoursAgo) {
return this.db(TABLE)
.whereRaw(`seen_at <= NOW() - INTERVAL '${hoursAgo} hours'`)
.del();
}
async deleteAll() {
await this.db(TABLE).delete();
}
async count({ unique } = {}) {
const countQuery = unique
? this.db(TABLE).countDistinct({ count: 'name' }).first()
: this.db(TABLE).count('* as count').first();
const row = await countQuery;
return Number(row?.count ?? 0);
}
}
//# sourceMappingURL=unknown-flags-store.js.map