@@ -18,9 +18,13 @@ import { NOOP_PROVIDER, ProviderStatus } from './provider';
1818
1919interface SetProviderOptions {
2020 /**
21- * If true, delays provider initialization until the first context change.
21+ * If provided, will be used to check if the current context is valid during initialization and context changes.
22+ * When calling `setProvider`, returning `false` will skip provider initialization. Throwing will move the provider to the ERROR state.
23+ * When calling `setProviderAndWait`, returning `false` will skip provider initialization. Throwing will reject the promise.
24+ * TODO: When calling `setContext`, returning `false` will skip provider context change handling. Throwing will move the provider to the ERROR state.
25+ * @param context The evaluation context to validate.
2226 */
23- delayed ?: boolean ;
27+ validateContext ?: ( context : EvaluationContext ) => boolean ;
2428}
2529
2630// use a symbol as a key for the global singleton
@@ -93,10 +97,11 @@ export class OpenFeatureAPI
9397 * Setting a provider supersedes the current provider used in new and existing unbound clients.
9498 * @param {Provider } provider The provider responsible for flag evaluations.
9599 * @param {EvaluationContext } context The evaluation context to use for flag evaluations.
100+ * @param {SetProviderOptions } [options] Options for setting the provider.
96101 * @returns {Promise<void> }
97102 * @throws {Error } If the provider throws an exception during initialization.
98103 */
99- setProviderAndWait ( provider : Provider , context : EvaluationContext ) : Promise < void > ;
104+ setProviderAndWait ( provider : Provider , context : EvaluationContext , options ?: SetProviderOptions ) : Promise < void > ;
100105 /**
101106 * Sets the provider that OpenFeature will use for flag evaluations on clients bound to the same domain.
102107 * A promise is returned that resolves when the provider is ready.
@@ -114,22 +119,38 @@ export class OpenFeatureAPI
114119 * @param {string } domain The name to identify the client
115120 * @param {Provider } provider The provider responsible for flag evaluations.
116121 * @param {EvaluationContext } context The evaluation context to use for flag evaluations.
122+ * @param {SetProviderOptions } [options] Options for setting the provider.
117123 * @returns {Promise<void> }
118124 * @throws {Error } If the provider throws an exception during initialization.
119125 */
120- setProviderAndWait ( domain : string , provider : Provider , context : EvaluationContext ) : Promise < void > ;
126+ setProviderAndWait ( domain : string , provider : Provider , context : EvaluationContext , options ?: SetProviderOptions ) : Promise < void > ;
121127 async setProviderAndWait (
122128 clientOrProvider ?: string | Provider ,
123129 providerContextOrUndefined ?: Provider | EvaluationContext ,
124- contextOrUndefined ?: EvaluationContext ,
130+ contextOptionsOrUndefined ?: EvaluationContext | SetProviderOptions ,
131+ optionsOrUndefined ?: SetProviderOptions ,
125132 ) : Promise < void > {
126133 const domain = stringOrUndefined ( clientOrProvider ) ;
127134 const provider = domain
128135 ? objectOrUndefined < Provider > ( providerContextOrUndefined )
129136 : objectOrUndefined < Provider > ( clientOrProvider ) ;
130137 const context = domain
131- ? objectOrUndefined < EvaluationContext > ( contextOrUndefined )
138+ ? objectOrUndefined < EvaluationContext > ( contextOptionsOrUndefined )
132139 : objectOrUndefined < EvaluationContext > ( providerContextOrUndefined ) ;
140+ const options = domain
141+ ? objectOrUndefined < SetProviderOptions > ( optionsOrUndefined )
142+ : objectOrUndefined < SetProviderOptions > ( contextOptionsOrUndefined ) ;
143+
144+ let skipInitialization = false ;
145+ if ( typeof options ?. validateContext === 'function' && context ) {
146+ // allow any error to propagate here to reject the promise
147+ skipInitialization = ! options . validateContext ( context ) ;
148+ if ( skipInitialization ) {
149+ this . _logger . debug (
150+ `Skipping provider initialization during setProviderAndWait for domain '${ domain ?? 'default' } ' due to validateContext returning false.` ,
151+ ) ;
152+ }
153+ }
133154
134155 if ( context ) {
135156 // synonymously setting context prior to provider initialization.
@@ -141,7 +162,7 @@ export class OpenFeatureAPI
141162 }
142163 }
143164
144- await this . setAwaitableProvider ( domain , provider ) ;
165+ await this . setAwaitableProvider ( domain , provider , skipInitialization ) ;
145166 }
146167
147168 /**
@@ -161,7 +182,7 @@ export class OpenFeatureAPI
161182 * @param {SetProviderOptions } [options] Options for setting the provider.
162183 * @returns {this } OpenFeature API
163184 */
164- setProvider < T extends SetProviderOptions > ( provider : Provider , context : T extends { delayed : true } ? Record < never , never > : EvaluationContext , options ?: T ) : this;
185+ setProvider ( provider : Provider , context : EvaluationContext , options ?: SetProviderOptions ) : this;
165186 /**
166187 * Sets the provider for flag evaluations of providers with the given name.
167188 * Setting a provider supersedes the current provider used in new and existing clients bound to the same domain.
@@ -179,7 +200,7 @@ export class OpenFeatureAPI
179200 * @param {SetProviderOptions } [options] Options for setting the provider.
180201 * @returns {this } OpenFeature API
181202 */
182- setProvider < T extends SetProviderOptions > ( domain : string , provider : Provider , context : T extends { delayed : true } ? Record < never , never > : EvaluationContext , options ?: T ) : this;
203+ setProvider ( domain : string , provider : Provider , context : EvaluationContext , options ?: SetProviderOptions ) : this;
183204 setProvider (
184205 domainOrProvider ?: string | Provider ,
185206 providerContextOrUndefined ?: Provider | EvaluationContext ,
@@ -197,6 +218,26 @@ export class OpenFeatureAPI
197218 ? objectOrUndefined < SetProviderOptions > ( optionsOrUndefined )
198219 : objectOrUndefined < SetProviderOptions > ( contextOptionsOrUndefined ) ;
199220
221+ let skipInitialization = false ;
222+ let validateContextError : unknown ;
223+ if ( typeof options ?. validateContext === 'function' && context ) {
224+ try {
225+ skipInitialization = ! options . validateContext ( context ) ;
226+ if ( skipInitialization ) {
227+ this . _logger . debug (
228+ `Skipping provider initialization during setProvider for domain '${ domain ?? 'default' } ' due to validateContext returning false.` ,
229+ ) ;
230+ }
231+ } catch ( err ) {
232+ // capture the error to move the provider to ERROR state after setting it
233+ validateContextError = err ;
234+ skipInitialization = true ;
235+ this . _logger . debug (
236+ `Skipping provider initialization during setProvider for domain '${ domain ?? 'default' } ' due to validateContext throwing an error.` ,
237+ ) ;
238+ }
239+ }
240+
200241 if ( context ) {
201242 // synonymously setting context prior to provider initialization.
202243 // No context change event will be emitted.
@@ -207,7 +248,34 @@ export class OpenFeatureAPI
207248 }
208249 }
209250
210- const maybePromise = this . setAwaitableProvider ( domain , provider , options ?. delayed ) ;
251+ const maybePromise = this . setAwaitableProvider ( domain , provider , skipInitialization ) ;
252+
253+ // If there was a validation error, move the provider to ERROR state.
254+ // In this situation we should never have a promise returned from initialization.
255+ if ( validateContextError ) {
256+ const wrapper = domain
257+ ? this . _domainScopedProviders . get ( domain )
258+ : this . _defaultProvider ;
259+ if ( wrapper ) {
260+ wrapper . status = this . _statusEnumType . ERROR ;
261+ const providerName = wrapper . provider ?. metadata ?. name || 'unnamed-provider' ;
262+ this . getAssociatedEventEmitters ( domain ) . forEach ( ( emitter ) => {
263+ emitter ?. emit ( ProviderEvents . Error , {
264+ clientName : domain ,
265+ domain,
266+ providerName,
267+ message : `Error validating context during setProvider: ${ validateContextError instanceof Error ? validateContextError . message : String ( validateContextError ) } ` ,
268+ } ) ;
269+ } ) ;
270+ this . _apiEmitter ?. emit ( ProviderEvents . Error , {
271+ clientName : domain ,
272+ domain,
273+ providerName,
274+ message : `Error validating context during setProvider: ${ validateContextError instanceof Error ? validateContextError . message : String ( validateContextError ) } ` ,
275+ } ) ;
276+ this . _logger . error ( 'Error validating context during setProvider:' , validateContextError ) ;
277+ }
278+ }
211279
212280 // The setProvider method doesn't return a promise so we need to catch and
213281 // log any errors that occur during provider initialization to avoid having
@@ -262,6 +330,8 @@ export class OpenFeatureAPI
262330 const domain = stringOrUndefined ( domainOrContext ) ;
263331 const context = objectOrUndefined < T > ( domainOrContext ) ?? objectOrUndefined ( contextOrUndefined ) ?? { } ;
264332
333+ // TODO: We need to store and call `validateContext` here if provided in `setProvider` options
334+
265335 if ( domain ) {
266336 const wrapper = this . _domainScopedProviders . get ( domain ) ;
267337 if ( wrapper ) {
0 commit comments