@@ -2,7 +2,6 @@ import type { EvaluationContext, Provider, JsonValue, ResolutionDetails, Logger
2
2
import { ErrorCode , ProviderEvents , StandardResolutionReasons } from '@openfeature/web-sdk' ;
3
3
import { EventEmitter } from 'events' ;
4
4
5
- // Interfaces for RocketFlag SDK
6
5
export interface UserContext {
7
6
cohort ?: string ;
8
7
}
@@ -16,126 +15,126 @@ interface RocketFlagClient {
16
15
}
17
16
18
17
/**
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 .
21
20
*/
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 ;
43
38
}
44
39
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 > = {
60
54
value : defaultValue ,
61
55
reason : StandardResolutionReasons . ERROR ,
62
56
errorCode : ErrorCode . GENERAL ,
63
- errorMessage : 'Invalid content in cache' ,
57
+ errorMessage : err . message ,
64
58
} ;
59
+ cache . set ( cacheKey , details ) ;
60
+ emitter . emit ( ProviderEvents . ConfigurationChanged , { flagsChanged : [ flagKey ] } ) ;
61
+ logger ?. error ( `Error fetching flag: ${ flagKey } ` , err ) ;
62
+ } ) ;
63
+ } ;
65
64
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
+ } ,
114
113
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 > => ( {
117
116
value : defaultValue ,
118
117
reason : StandardResolutionReasons . ERROR ,
119
118
errorCode : ErrorCode . TYPE_MISMATCH ,
120
119
errorMessage : 'RocketFlag: String flags are not yet supported.' ,
121
- } ;
122
- }
120
+ } ) ,
123
121
124
- resolveNumberEvaluation ( flagKey : string , defaultValue : number ) : ResolutionDetails < number > {
125
- return {
122
+ resolveNumberEvaluation : ( flagKey : string , defaultValue : number ) : ResolutionDetails < number > => ( {
126
123
value : defaultValue ,
127
124
reason : StandardResolutionReasons . ERROR ,
128
125
errorCode : ErrorCode . TYPE_MISMATCH ,
129
126
errorMessage : 'RocketFlag: Number flags are not yet supported.' ,
130
- } ;
131
- }
127
+ } ) ,
132
128
133
- resolveObjectEvaluation < U extends JsonValue > ( flagKey : string , defaultValue : U ) : ResolutionDetails < U > {
134
- return {
129
+ resolveObjectEvaluation : < U extends JsonValue > ( flagKey : string , defaultValue : U ) : ResolutionDetails < U > => ( {
135
130
value : defaultValue ,
136
131
reason : StandardResolutionReasons . ERROR ,
137
132
errorCode : ErrorCode . TYPE_MISMATCH ,
138
133
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 ) ;
141
140
}
0 commit comments