Skip to main content
Glama
reflagcom

Bucket Feature Flags MCP Server

Official
by reflagcom
flagCache.ts3.86 kB
import { RawFlags } from "./flags"; interface StorageItem { get(): string | null; set(value: string): void; } interface cacheEntry { expireAt: number; staleAt: number; flags: RawFlags; } // Parse and validate an API flags response export function parseAPIFlagsResponse(flagsInput: any): RawFlags | undefined { if (!isObject(flagsInput)) { return; } const flags: RawFlags = {}; for (const key in flagsInput) { const flag = flagsInput[key]; if ( typeof flag.isEnabled !== "boolean" || flag.key !== key || typeof flag.targetingVersion !== "number" || (flag.config && typeof flag.config !== "object") || (flag.missingContextFields && !Array.isArray(flag.missingContextFields)) || (flag.ruleEvaluationResults && !Array.isArray(flag.ruleEvaluationResults)) ) { return; } flags[key] = { isEnabled: flag.isEnabled, targetingVersion: flag.targetingVersion, key, config: flag.config, missingContextFields: flag.missingContextFields, ruleEvaluationResults: flag.ruleEvaluationResults, }; } return flags; } export interface CacheResult { flags: RawFlags; stale: boolean; } export class FlagCache { private storage: StorageItem; private readonly staleTimeMs: number; private readonly expireTimeMs: number; constructor({ storage, staleTimeMs, expireTimeMs, }: { storage: StorageItem; staleTimeMs: number; expireTimeMs: number; }) { this.storage = storage; this.staleTimeMs = staleTimeMs; this.expireTimeMs = expireTimeMs; } set( key: string, { flags, }: { flags: RawFlags; }, ) { let cacheData: CacheData = {}; try { const cachedResponseRaw = this.storage.get(); if (cachedResponseRaw) { cacheData = validateCacheData(JSON.parse(cachedResponseRaw)) ?? {}; } } catch { // ignore errors } cacheData[key] = { expireAt: Date.now() + this.expireTimeMs, staleAt: Date.now() + this.staleTimeMs, flags, } satisfies cacheEntry; cacheData = Object.fromEntries( Object.entries(cacheData).filter(([_k, v]) => v.expireAt > Date.now()), ); this.storage.set(JSON.stringify(cacheData)); return cacheData; } get(key: string): CacheResult | undefined { try { const cachedResponseRaw = this.storage.get(); if (cachedResponseRaw) { const cachedResponse = validateCacheData(JSON.parse(cachedResponseRaw)); if ( cachedResponse && cachedResponse[key] && cachedResponse[key].expireAt > Date.now() ) { return { flags: cachedResponse[key].flags, stale: cachedResponse[key].staleAt < Date.now(), }; } } } catch { // ignore errors } return; } } type CacheData = Record<string, cacheEntry>; function validateCacheData(cacheDataInput: any) { if (!isObject(cacheDataInput)) { return; } const cacheData: CacheData = {}; for (const key in cacheDataInput) { const cacheEntry = cacheDataInput[key]; if (!isObject(cacheEntry)) return; if ( typeof cacheEntry.expireAt !== "number" || typeof cacheEntry.staleAt !== "number" || (cacheEntry.flags && !parseAPIFlagsResponse(cacheEntry.flags)) ) { return; } cacheData[key] = { expireAt: cacheEntry.expireAt, staleAt: cacheEntry.staleAt, flags: cacheEntry.flags, }; } return cacheData; } /** * Check if the given item is an object. * * @param item - The item to check. * @returns `true` if the item is an object, `false` otherwise. **/ export function isObject(item: any): item is Record<string, any> { return (item && typeof item === "object" && !Array.isArray(item)) || false; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reflagcom/bucket-javascript-sdk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server