@@ -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,33 @@ 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+ }
133149
134150 if ( context ) {
135151 // synonymously setting context prior to provider initialization.
@@ -141,7 +157,7 @@ export class OpenFeatureAPI
141157 }
142158 }
143159
144- await this . setAwaitableProvider ( domain , provider ) ;
160+ await this . setAwaitableProvider ( domain , provider , skipInitialization ) ;
145161 }
146162
147163 /**
@@ -161,7 +177,7 @@ export class OpenFeatureAPI
161177 * @param {SetProviderOptions } [options] Options for setting the provider.
162178 * @returns {this } OpenFeature API
163179 */
164- setProvider < T extends SetProviderOptions > ( provider : Provider , context : T extends { delayed : true } ? Record < never , never > : EvaluationContext , options ?: T ) : this;
180+ setProvider ( provider : Provider , context : EvaluationContext , options ?: SetProviderOptions ) : this;
165181 /**
166182 * Sets the provider for flag evaluations of providers with the given name.
167183 * Setting a provider supersedes the current provider used in new and existing clients bound to the same domain.
@@ -179,7 +195,7 @@ export class OpenFeatureAPI
179195 * @param {SetProviderOptions } [options] Options for setting the provider.
180196 * @returns {this } OpenFeature API
181197 */
182- setProvider < T extends SetProviderOptions > ( domain : string , provider : Provider , context : T extends { delayed : true } ? Record < never , never > : EvaluationContext , options ?: T ) : this;
198+ setProvider ( domain : string , provider : Provider , context : EvaluationContext , options ?: SetProviderOptions ) : this;
183199 setProvider (
184200 domainOrProvider ?: string | Provider ,
185201 providerContextOrUndefined ?: Provider | EvaluationContext ,
@@ -197,6 +213,26 @@ export class OpenFeatureAPI
197213 ? objectOrUndefined < SetProviderOptions > ( optionsOrUndefined )
198214 : objectOrUndefined < SetProviderOptions > ( contextOptionsOrUndefined ) ;
199215
216+ let skipInitialization = false ;
217+ let validateContextError : unknown ;
218+ if ( typeof options ?. validateContext === 'function' && context ) {
219+ try {
220+ skipInitialization = ! options . validateContext ( context ) ;
221+ if ( skipInitialization ) {
222+ this . _logger . debug (
223+ `Skipping provider initialization during setProvider for domain '${ domain ?? 'default' } ' due to validateContext returning false.` ,
224+ ) ;
225+ }
226+ } catch ( err ) {
227+ // capture the error to move the provider to ERROR state after setting it
228+ validateContextError = err ;
229+ skipInitialization = true ;
230+ this . _logger . debug (
231+ `Skipping provider initialization during setProvider for domain '${ domain ?? 'default' } ' due to validateContext throwing an error.` ,
232+ ) ;
233+ }
234+ }
235+
200236 if ( context ) {
201237 // synonymously setting context prior to provider initialization.
202238 // No context change event will be emitted.
@@ -207,7 +243,34 @@ export class OpenFeatureAPI
207243 }
208244 }
209245
210- const maybePromise = this . setAwaitableProvider ( domain , provider , options ?. delayed ) ;
246+ const maybePromise = this . setAwaitableProvider ( domain , provider , skipInitialization ) ;
247+
248+ // If there was a validation error, move the provider to ERROR state.
249+ // In this situation we should never have a promise returned from initialization.
250+ if ( validateContextError ) {
251+ const wrapper = domain
252+ ? this . _domainScopedProviders . get ( domain )
253+ : this . _defaultProvider ;
254+ if ( wrapper ) {
255+ wrapper . status = this . _statusEnumType . ERROR ;
256+ const providerName = wrapper . provider ?. metadata ?. name || 'unnamed-provider' ;
257+ this . getAssociatedEventEmitters ( domain ) . forEach ( ( emitter ) => {
258+ emitter ?. emit ( ProviderEvents . Error , {
259+ clientName : domain ,
260+ domain,
261+ providerName,
262+ message : `Error validating context during setProvider: ${ validateContextError instanceof Error ? validateContextError . message : String ( validateContextError ) } ` ,
263+ } ) ;
264+ } ) ;
265+ this . _apiEmitter ?. emit ( ProviderEvents . Error , {
266+ clientName : domain ,
267+ domain,
268+ providerName,
269+ message : `Error validating context during setProvider: ${ validateContextError instanceof Error ? validateContextError . message : String ( validateContextError ) } ` ,
270+ } ) ;
271+ this . _logger . error ( 'Error validating context during setProvider:' , validateContextError ) ;
272+ }
273+ }
211274
212275 // The setProvider method doesn't return a promise so we need to catch and
213276 // log any errors that occur during provider initialization to avoid having
@@ -262,6 +325,8 @@ export class OpenFeatureAPI
262325 const domain = stringOrUndefined ( domainOrContext ) ;
263326 const context = objectOrUndefined < T > ( domainOrContext ) ?? objectOrUndefined ( contextOrUndefined ) ?? { } ;
264327
328+ // TODO: We need to store and call `validateContext` here if provided in `setProvider` options
329+
265330 if ( domain ) {
266331 const wrapper = this . _domainScopedProviders . get ( domain ) ;
267332 if ( wrapper ) {
0 commit comments