@@ -2,7 +2,6 @@ import type { EvaluationContext, Provider, JsonValue, ResolutionDetails, Logger
22import { ErrorCode , ProviderEvents , StandardResolutionReasons } from '@openfeature/web-sdk' ;
33import { EventEmitter } from 'events' ;
44
5- // Interfaces for RocketFlag SDK
65export interface UserContext {
76 cohort ?: string ;
87}
@@ -16,126 +15,126 @@ interface RocketFlagClient {
1615}
1716
1817/**
19- * The RocketFlagProvider implements the OpenFeature Provider interface
20- * to resolve feature flags from the RocketFlag service .
18+ * A helper function to fetch a flag, update the cache, and notify OpenFeature of changes.
19+ * It's defined within the factory's scope to access the client, cache, emitter, and logger .
2120 */
22- export class RocketFlagProvider extends EventEmitter implements Provider {
23- metadata = {
24- name : RocketFlagProvider . name ,
25- } ;
26-
27- readonly runsOn = 'client' ;
28- hooks = [ ] ;
29- private client : RocketFlagClient ;
30- private cache : Map < string , ResolutionDetails < boolean > > = new Map ( ) ;
31- private logger ?: Logger ;
32-
33- constructor ( client : RocketFlagClient ) {
34- super ( ) ;
35- this . client = client ;
36- }
37-
38- /**
39- * Initialises the provider and can be used to pre-fetch flags.
40- */
41- async initialize ( ) : Promise < void > {
42- this . logger ?. debug ( 'Initialising RocketFlagProvider...' ) ;
21+ const fetchFlagAndUpdateCache = (
22+ // Parameters required for the fetch operation
23+ flagKey : string ,
24+ defaultValue : boolean ,
25+ context : EvaluationContext ,
26+ cacheKey : string ,
27+ // State managed by the factory's closure
28+ client : RocketFlagClient ,
29+ cache : Map < string , ResolutionDetails < boolean > > ,
30+ emitter : EventEmitter ,
31+ logger ?: Logger ,
32+ ) => {
33+ const userContext : UserContext = { } ;
34+ const { targetingKey } = context ;
35+
36+ if ( targetingKey && typeof targetingKey === 'string' && targetingKey !== '' ) {
37+ userContext . cohort = targetingKey ;
4338 }
4439
45- resolveBooleanEvaluation (
46- flagKey : string ,
47- defaultValue : boolean ,
48- context : EvaluationContext ,
49- logger : Logger ,
50- ) : ResolutionDetails < boolean > {
51- this . logger = logger ;
52- const cacheKey = JSON . stringify ( { flagKey, context } ) ;
53-
54- // The SDK expects a synchronous return, so we fetch in the background and notify OpenFeature when the value is ready.
55- this . fetchFlagAndUpdateCache ( flagKey , defaultValue , context , cacheKey ) ;
56-
57- // Immediately return a value from the cache if available.
58- if ( this . cache . has ( cacheKey ) ) {
59- const invalidCacheContent : ResolutionDetails < boolean > = {
40+ client
41+ . getFlag ( flagKey , userContext )
42+ . then ( ( flagStatus ) => {
43+ const details : ResolutionDetails < boolean > = {
44+ value : flagStatus . enabled ,
45+ reason : userContext . cohort ? StandardResolutionReasons . TARGETING_MATCH : StandardResolutionReasons . DEFAULT ,
46+ } ;
47+ cache . set ( cacheKey , details ) ;
48+ emitter . emit ( ProviderEvents . ConfigurationChanged , { flagsChanged : [ flagKey ] } ) ;
49+ logger ?. debug ( `Successfully fetched flag: ${ flagKey } ` ) ;
50+ } )
51+ . catch ( ( error : unknown ) => {
52+ const err = error instanceof Error ? error : new Error ( String ( error ) ) ;
53+ const details : ResolutionDetails < boolean > = {
6054 value : defaultValue ,
6155 reason : StandardResolutionReasons . ERROR ,
6256 errorCode : ErrorCode . GENERAL ,
63- errorMessage : 'Invalid content in cache' ,
57+ errorMessage : err . message ,
6458 } ;
59+ cache . set ( cacheKey , details ) ;
60+ emitter . emit ( ProviderEvents . ConfigurationChanged , { flagsChanged : [ flagKey ] } ) ;
61+ logger ?. error ( `Error fetching flag: ${ flagKey } ` , err ) ;
62+ } ) ;
63+ } ;
6564
66- // Since the .get method can return undefined, we handle that with a default "invalid cache" value.
67- return this . cache . get ( cacheKey ) || invalidCacheContent ;
68- }
69-
70- // Return the default value immediately. The cache will be updated later .
71- return {
72- value : defaultValue ,
73- reason : StandardResolutionReasons . STALE ,
74- } ;
75- }
76-
77- private fetchFlagAndUpdateCache (
78- flagKey : string ,
79- defaultValue : boolean ,
80- context : EvaluationContext ,
81- cacheKey : string ,
82- ) {
83- const userContext : UserContext = { } ;
84- const targetingKey = context . targetingKey ;
85-
86- if ( targetingKey && typeof targetingKey === 'string' && targetingKey !== '' ) {
87- userContext . cohort = targetingKey ;
88- }
89-
90- this . client
91- . getFlag ( flagKey , userContext )
92- . then ( ( flagStatus ) => {
93- const details : ResolutionDetails < boolean > = {
94- value : flagStatus . enabled ,
95- reason : userContext . cohort ? StandardResolutionReasons . TARGETING_MATCH : StandardResolutionReasons . DEFAULT ,
96- } ;
97- this . cache . set ( cacheKey , details ) ;
98- this . emit ( ProviderEvents . ConfigurationChanged , { flagsChanged : [ flagKey ] } ) ;
99- this . logger ?. debug ( `Successfully fetched flag: ${ flagKey } ` ) ;
100- } )
101- . catch ( ( error : unknown ) => {
102- const err = error instanceof Error ? error : new Error ( String ( error ) ) ;
103- const details : ResolutionDetails < boolean > = {
104- value : defaultValue ,
105- reason : StandardResolutionReasons . ERROR ,
106- errorCode : ErrorCode . GENERAL ,
107- errorMessage : err . message ,
108- } ;
109- this . cache . set ( cacheKey , details ) ;
110- this . emit ( ProviderEvents . ConfigurationChanged , { flagsChanged : [ flagKey ] } ) ;
111- this . logger ?. error ( `Error fetching flag: ${ flagKey } ` , err ) ;
112- } ) ;
113- }
65+ /**
66+ * Creates a functional OpenFeature provider for RocketFlag.
67+ * This provider resolves boolean flags from the RocketFlag service.
68+ *
69+ * @param { RocketFlagClient } client - An instance of the RocketFlag client .
70+ * @returns { Provider & EventEmitter } A provider instance that can be used with the OpenFeature SDK.
71+ */
72+ export function createRocketFlagProvider ( client : RocketFlagClient ) : Provider & EventEmitter {
73+ const emitter = new EventEmitter ( ) ;
74+ const cache = new Map < string , ResolutionDetails < boolean > > ( ) ;
75+ let logger : Logger | undefined ;
76+
77+ // Define the provider's logic in a plain object.
78+ const providerLogic = {
79+ metadata : {
80+ name : 'RocketFlagProvider' ,
81+ } ,
82+ runsOn : 'client' as const ,
83+ hooks : [ ] ,
84+
85+ initialize : async ( ) : Promise < void > => {
86+ logger ?. debug ( 'Initialising RocketFlagProvider...' ) ;
87+ } ,
88+
89+ resolveBooleanEvaluation : (
90+ flagKey : string ,
91+ defaultValue : boolean ,
92+ context : EvaluationContext ,
93+ evalLogger : Logger ,
94+ ) : ResolutionDetails < boolean > => {
95+ logger = evalLogger ; // Capture the logger for async operations.
96+ const cacheKey = JSON . stringify ( { flagKey , context } ) ;
97+
98+ // Fetch in the background.
99+ fetchFlagAndUpdateCache ( flagKey , defaultValue , context , cacheKey , client , cache , emitter , logger ) ;
100+
101+ // Immediately return a cached value if available.
102+ if ( cache . has ( cacheKey ) ) {
103+ // The .get() method can return undefined, so we handle that case.
104+ return cache . get ( cacheKey ) as ResolutionDetails < boolean > ;
105+ }
106+
107+ // Return a STALE value while the fetch is in progress.
108+ return {
109+ value : defaultValue ,
110+ reason : StandardResolutionReasons . STALE ,
111+ } ;
112+ } ,
114113
115- resolveStringEvaluation ( flagKey : string , defaultValue : string ) : ResolutionDetails < string > {
116- return {
114+ // The other evaluation methods simply return an error.
115+ resolveStringEvaluation : ( flagKey : string , defaultValue : string ) : ResolutionDetails < string > => ( {
117116 value : defaultValue ,
118117 reason : StandardResolutionReasons . ERROR ,
119118 errorCode : ErrorCode . TYPE_MISMATCH ,
120119 errorMessage : 'RocketFlag: String flags are not yet supported.' ,
121- } ;
122- }
120+ } ) ,
123121
124- resolveNumberEvaluation ( flagKey : string , defaultValue : number ) : ResolutionDetails < number > {
125- return {
122+ resolveNumberEvaluation : ( flagKey : string , defaultValue : number ) : ResolutionDetails < number > => ( {
126123 value : defaultValue ,
127124 reason : StandardResolutionReasons . ERROR ,
128125 errorCode : ErrorCode . TYPE_MISMATCH ,
129126 errorMessage : 'RocketFlag: Number flags are not yet supported.' ,
130- } ;
131- }
127+ } ) ,
132128
133- resolveObjectEvaluation < U extends JsonValue > ( flagKey : string , defaultValue : U ) : ResolutionDetails < U > {
134- return {
129+ resolveObjectEvaluation : < U extends JsonValue > ( flagKey : string , defaultValue : U ) : ResolutionDetails < U > => ( {
135130 value : defaultValue ,
136131 reason : StandardResolutionReasons . ERROR ,
137132 errorCode : ErrorCode . TYPE_MISMATCH ,
138133 errorMessage : 'RocketFlag: Object flags are not yet supported.' ,
139- } ;
140- }
134+ } ) ,
135+ } ;
136+
137+ // The OpenFeature SDK expects the provider itself to be an event emitter.
138+ // We merge the EventEmitter instance with our provider logic.
139+ return Object . assign ( emitter , providerLogic ) ;
141140}
0 commit comments