import type { PostHogFeatureFlag, PropertyGroup } from '../../types' /** * Represents the complete set of feature flag data needed for local evaluation. * * This includes flag definitions, group type mappings, and cohort property groups. * Newly published data includes snake_case keys and camelCase aliases so existing * providers and older Node SDKs can continue to read the same cache. */ export interface FlagDefinitionCacheData { /** Array of feature flag definitions */ flags: PostHogFeatureFlag[] /** Mapping of group type index to group name, using the flag-definitions endpoint's field name. */ group_type_mapping?: Record /** Legacy alias for group_type_mapping, retained for existing providers and cache readers. */ groupTypeMapping: Record /** Cohort property groups for local evaluation */ cohorts: Record /** * Server-controlled gate for minimal `$feature_flag_called` events, from the top-level * `minimal_flag_called_events` field of the flag-definitions payload. If neither this * field nor its legacy alias is present, full events are sent. */ minimal_flag_called_events?: boolean /** Legacy alias for minimal_flag_called_events. */ minimalFlagCalledEvents?: boolean /** Top-level definitions matching version; absent in older caches means legacy matching. */ property_matching_version?: number /** Legacy alias for property_matching_version. */ propertyMatchingVersion?: number } /** * Cache reads accept endpoint-shaped snake_case data or older camelCase data. * When both forms are present, snake_case takes precedence. */ export type FlagDefinitionCacheInput = | FlagDefinitionCacheData | (Omit & { group_type_mapping: Record groupTypeMapping?: Record }) /** * Provider interface for caching feature flag definitions. * * Implementations can use this to control when flag definitions are fetched * and how they're cached (Redis, database, filesystem, etc.). * * This interface is designed for server-side environments where multiple workers * need to share flag definitions and coordinate fetching to reduce API calls. * Without a secretKey or personalApiKey, instances are cache-only readers: each refresh * reads cached definitions without calling shouldFetchFlagDefinitions or publishing data. * Instances with a credential coordinate fetching and publish fresh definitions. * * All methods may throw errors - the poller will catch and log them gracefully, * ensuring cache provider errors never break flag evaluation. * * @typeParam CacheData - The shape returned by cache reads. Use FlagDefinitionCacheInput * to accept snake_case entries as well as legacy camelCase entries. * * @example * ```typescript * import type { FlagDefinitionCacheData, FlagDefinitionCacheInput, FlagDefinitionCacheProvider } from 'posthog-node' * * class RedisFlagCache implements FlagDefinitionCacheProvider { * constructor(private redis: Redis, private teamKey: string) { } * * async getFlagDefinitions(): Promise { * const cached = await this.redis.get(`posthog:flags:${this.teamKey}`) * return cached ? JSON.parse(cached) : undefined * } * * async shouldFetchFlagDefinitions(): Promise { * // Acquire distributed lock - only one worker fetches * const acquired = await this.redis.set(`posthog:flags:${this.teamKey}:lock`, '1', 'EX', 60, 'NX') * return acquired === 'OK' * } * * async onFlagDefinitionsReceived(data: FlagDefinitionCacheData): Promise { * await this.redis.set(`posthog:flags:${this.teamKey}`, JSON.stringify(data), 'EX', 300) * await this.redis.del(`posthog:flags:${this.teamKey}:lock`) * } * * async shutdown(): Promise { * await this.redis.del(`posthog:flags:${this.teamKey}:lock`) * } * } * ``` */ export interface FlagDefinitionCacheProvider { /** * Retrieve cached flag definitions. * * Called when the poller is refreshing in-memory flag definitions. If this returns undefined * (or throws an error), the poller will fetch fresh data from the PostHog API if no flag * definitions are in memory and a secretKey or personalApiKey is configured. Otherwise, * existing in-memory definitions are retained until the next refresh. * * @returns cached definitions if available, undefined if cache is empty * @throws if an error occurs while accessing the cache (error will be logged) */ getFlagDefinitions(): Promise | CacheData | undefined /** * Determines whether this instance should fetch new flag definitions. * * Called only when a secretKey or personalApiKey is configured. Cache-only readers * skip this method and read cached definitions directly on each refresh. * Use this to implement distributed coordination (e.g., via distributed locks) * to ensure only one credentialed instance fetches at a time in a multi-worker setup. * * When multiple workers share a cache, typically only one should fetch while * others use cached data. Implementations can use Redis locks, database locks, * or other coordination mechanisms. * * @returns true if this instance should fetch, false to skip and read cache * @throws if coordination backend is unavailable (error will be logged, fetch continues) */ shouldFetchFlagDefinitions(): Promise | boolean /** * Called after successfully receiving new flag definitions from PostHog. * * Store the definitions in your cache backend here. This is called only * after a successful API response with valid flag data. * * If this method throws, the error is logged but flag definitions are still * stored in memory, ensuring local evaluation can still be performed. * * @param data - The complete flag definition data from PostHog * @throws if storage backend is unavailable (error will be logged) */ onFlagDefinitionsReceived(data: FlagDefinitionCacheData): Promise | void /** * Called when the PostHog client shuts down. * * Release any held locks, close connections, or clean up resources here. * * Both sync and async cleanup are supported. Async cleanup has a timeout * (default 30s, configurable via client shutdown options) to prevent the * process shutdown from hanging indefinitely. * * @returns Promise that resolves when cleanup is complete, or void for sync cleanup */ shutdown(): Promise | void }