diff --git a/packages/client/src/client/open-feature-client.ts b/packages/client/src/client/open-feature-client.ts index e11fc400..7fb9c52c 100644 --- a/packages/client/src/client/open-feature-client.ts +++ b/packages/client/src/client/open-feature-client.ts @@ -6,7 +6,6 @@ import { EventHandler, FlagValue, FlagValueType, - Hook, HookContext, JsonValue, Logger, @@ -22,6 +21,7 @@ import { OpenFeature } from '../open-feature'; import { Provider } from '../provider'; import { InternalEventEmitter } from '../events/internal/internal-event-emitter'; import { Client } from './client'; +import { Hook } from '../hooks'; type OpenFeatureClientOptions = { name?: string; @@ -38,7 +38,7 @@ export class OpenFeatureClient implements Client { private readonly providerAccessor: () => Provider, private readonly emitterAccessor: () => InternalEventEmitter, private readonly globalLogger: () => Logger, - private readonly options: OpenFeatureClientOptions + private readonly options: OpenFeatureClientOptions, ) {} get metadata(): ClientMetadata { @@ -76,12 +76,12 @@ export class OpenFeatureClient implements Client { return this; } - addHooks(...hooks: Hook[]): this { + addHooks(...hooks: Hook[]): this { this._hooks = [...this._hooks, ...hooks]; return this; } - getHooks(): Hook[] { + getHooks(): Hook[] { return this._hooks; } @@ -97,7 +97,7 @@ export class OpenFeatureClient implements Client { getBooleanDetails( flagKey: string, defaultValue: boolean, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails { return this.evaluate(flagKey, this._provider.resolveBooleanEvaluation, defaultValue, 'boolean', options); } @@ -109,7 +109,7 @@ export class OpenFeatureClient implements Client { getStringDetails( flagKey: string, defaultValue: T, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails { return this.evaluate( flagKey, @@ -117,7 +117,7 @@ export class OpenFeatureClient implements Client { this._provider.resolveStringEvaluation as () => EvaluationDetails, defaultValue, 'string', - options + options, ); } @@ -128,7 +128,7 @@ export class OpenFeatureClient implements Client { getNumberDetails( flagKey: string, defaultValue: T, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails { return this.evaluate( flagKey, @@ -136,14 +136,14 @@ export class OpenFeatureClient implements Client { this._provider.resolveNumberEvaluation as () => EvaluationDetails, defaultValue, 'number', - options + options, ); } getObjectValue( flagKey: string, defaultValue: T, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): T { return this.getObjectDetails(flagKey, defaultValue, options).value; } @@ -151,7 +151,7 @@ export class OpenFeatureClient implements Client { getObjectDetails( flagKey: string, defaultValue: T, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails { return this.evaluate(flagKey, this._provider.resolveObjectEvaluation, defaultValue, 'object', options); } @@ -161,7 +161,7 @@ export class OpenFeatureClient implements Client { resolver: (flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger) => ResolutionDetails, defaultValue: T, flagType: FlagValueType, - options: FlagEvaluationOptions = {} + options: FlagEvaluationOptions = {}, ): EvaluationDetails { // merge global, client, and evaluation context @@ -224,26 +224,19 @@ export class OpenFeatureClient implements Client { } private beforeHooks(hooks: Hook[], hookContext: HookContext, options: FlagEvaluationOptions) { + Object.freeze(hookContext); + Object.freeze(hookContext.context); + for (const hook of hooks) { - // freeze the hookContext - Object.freeze(hookContext); - - // use Object.assign to avoid modification of frozen hookContext - Object.assign(hookContext.context, { - ...hookContext.context, - ...hook?.before?.(hookContext, Object.freeze(options.hookHints)), - }); + hook?.before?.(hookContext, Object.freeze(options.hookHints)); } - - // after before hooks, freeze the EvaluationContext. - return Object.freeze(hookContext.context); } private afterHooks( hooks: Hook[], hookContext: HookContext, evaluationDetails: EvaluationDetails, - options: FlagEvaluationOptions + options: FlagEvaluationOptions, ) { // run "after" hooks sequentially for (const hook of hooks) { diff --git a/packages/client/src/evaluation/evaluation.ts b/packages/client/src/evaluation/evaluation.ts index 8555dd53..05078724 100644 --- a/packages/client/src/evaluation/evaluation.ts +++ b/packages/client/src/evaluation/evaluation.ts @@ -1,7 +1,7 @@ -import { EvaluationDetails, Hook, HookHints, JsonValue } from '@openfeature/core'; +import { EvaluationDetails, BaseHook, HookHints, JsonValue } from '@openfeature/core'; export interface FlagEvaluationOptions { - hooks?: Hook[]; + hooks?: BaseHook[]; hookHints?: HookHints; } @@ -25,7 +25,7 @@ export interface Features { getBooleanDetails( flagKey: string, defaultValue: boolean, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails; /** @@ -53,7 +53,7 @@ export interface Features { getStringDetails( flagKey: string, defaultValue: T, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails; /** @@ -81,7 +81,7 @@ export interface Features { getNumberDetails( flagKey: string, defaultValue: T, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails; /** @@ -107,12 +107,12 @@ export interface Features { getObjectDetails( flagKey: string, defaultValue: JsonValue, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails; getObjectDetails( flagKey: string, defaultValue: T, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): EvaluationDetails; } diff --git a/packages/client/src/hooks/hook.ts b/packages/client/src/hooks/hook.ts new file mode 100644 index 00000000..e824c149 --- /dev/null +++ b/packages/client/src/hooks/hook.ts @@ -0,0 +1,3 @@ +import { BaseHook, FlagValue } from '@openfeature/core'; + +export type Hook = BaseHook; diff --git a/packages/client/src/hooks/index.ts b/packages/client/src/hooks/index.ts new file mode 100644 index 00000000..1146a6d5 --- /dev/null +++ b/packages/client/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './hook'; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index c6dfb63d..355cd9ce 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -3,4 +3,5 @@ export * from './provider'; export * from './evaluation'; export * from './open-feature'; export * from './events'; +export * from './hooks'; export * from '@openfeature/core'; diff --git a/packages/client/src/open-feature.ts b/packages/client/src/open-feature.ts index cb27bd93..b2454bf0 100644 --- a/packages/client/src/open-feature.ts +++ b/packages/client/src/open-feature.ts @@ -2,6 +2,7 @@ import { EvaluationContext, ManageContext, OpenFeatureCommonAPI } from '@openfea import { Client, OpenFeatureClient } from './client'; import { NOOP_PROVIDER, Provider } from './provider'; import { OpenFeatureEventEmitter } from './events'; +import { Hook } from './hooks'; // use a symbol as a key for the global singleton const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/web-sdk/api'); @@ -11,7 +12,7 @@ type OpenFeatureGlobal = { }; const _globalThis = globalThis as OpenFeatureGlobal; -export class OpenFeatureAPI extends OpenFeatureCommonAPI implements ManageContext> { +export class OpenFeatureAPI extends OpenFeatureCommonAPI implements ManageContext> { protected _events = new OpenFeatureEventEmitter(); protected _defaultProvider: Provider = NOOP_PROVIDER; protected _createEventEmitter = () => new OpenFeatureEventEmitter(); @@ -49,7 +50,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI implements Ma } catch (err) { this._logger?.error(`Error running context change handler of provider ${provider.metadata.name}:`, err); } - }) + }), ); } @@ -75,7 +76,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI implements Ma () => this.getProviderForClient(name), () => this.buildAndCacheEventEmitterForClient(name), () => this._logger, - { name, version } + { name, version }, ); } diff --git a/packages/client/src/provider/provider.ts b/packages/client/src/provider/provider.ts index 16a4bafe..f15b01e6 100644 --- a/packages/client/src/provider/provider.ts +++ b/packages/client/src/provider/provider.ts @@ -1,4 +1,5 @@ -import { CommonProvider, EvaluationContext, Hook, JsonValue, Logger, ResolutionDetails } from '@openfeature/core'; +import { CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfeature/core'; +import { Hook } from '../hooks'; /** * Interface that providers must implement to resolve flag values for their particular @@ -30,7 +31,7 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: boolean, context: EvaluationContext, - logger: Logger + logger: Logger, ): ResolutionDetails; /** @@ -40,7 +41,7 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: string, context: EvaluationContext, - logger: Logger + logger: Logger, ): ResolutionDetails; /** @@ -50,7 +51,7 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: number, context: EvaluationContext, - logger: Logger + logger: Logger, ): ResolutionDetails; /** @@ -60,6 +61,6 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: T, context: EvaluationContext, - logger: Logger + logger: Logger, ): ResolutionDetails; } diff --git a/packages/client/test/hooks.spec.ts b/packages/client/test/hooks.spec.ts index 7f94f85c..72eeccf0 100644 --- a/packages/client/test/hooks.spec.ts +++ b/packages/client/test/hooks.spec.ts @@ -4,9 +4,9 @@ import { Client, FlagValueType, EvaluationContext, - Hook, GeneralError, OpenFeature, + Hook, } from '../src'; const BOOLEAN_VALUE = true; @@ -107,27 +107,6 @@ describe('Hooks', () => { }); describe('Requirement 4.1.4', () => { - describe('before', () => { - it('evaluationContext must be mutable', (done) => { - client.getBooleanValue(FLAG_KEY, false, { - hooks: [ - { - before: (hookContext) => { - try { - // evaluation context is mutable in before, so this should work. - hookContext.context.newBeforeProp = 'new!'; - expect(hookContext.context.newBeforeProp).toBeTruthy(); - done(); - } catch (err) { - done(err); - } - }, - }, - ], - }); - }); - }); - describe('after', () => { it('evaluationContext must be immutable', (done) => { client.getBooleanValue(FLAG_KEY, false, { @@ -151,58 +130,6 @@ describe('Hooks', () => { }); }); - describe('4.3.2', () => { - it('"before" must run before flag resolution', async () => { - await client.getBooleanValue(FLAG_KEY, false, { - hooks: [ - { - before: () => { - // add a prop to the context. - return { beforeRan: true }; - }, - }, - ], - }); - - expect(MOCK_PROVIDER.resolveBooleanEvaluation).toHaveBeenCalledWith( - expect.anything(), - expect.anything(), - // ensure property was added by the time the flag resolution occurred. - expect.objectContaining({ - beforeRan: true, - }), - expect.anything() - ); - }); - }); - - describe('Requirement 4.3.3', () => { - it('EvaluationContext must be passed to next "before" hook', (done) => { - client.getBooleanValue(FLAG_KEY, false, { - hooks: [ - { - before: () => { - // add a prop to the context. - return { beforeRan: true }; - }, - }, - { - before: (hookContext) => { - // ensure added prop exists in next hook - try { - expect(hookContext.context.beforeRan).toBeTruthy(); - done(); - } catch (err) { - done(err); - } - return { beforeRan: true }; - }, - }, - ], - }); - }); - }); - describe('Requirement 4.3.5', () => { it('"after" must run after flag evaluation', (done) => { client.getBooleanValue(FLAG_KEY, false, { diff --git a/packages/server/src/client/open-feature-client.ts b/packages/server/src/client/open-feature-client.ts index 9020bbdb..35322269 100644 --- a/packages/server/src/client/open-feature-client.ts +++ b/packages/server/src/client/open-feature-client.ts @@ -6,7 +6,6 @@ import { EventHandler, FlagValue, FlagValueType, - Hook, HookContext, JsonValue, Logger, @@ -16,13 +15,14 @@ import { ResolutionDetails, SafeLogger, StandardResolutionReasons, - statusMatchesEvent + statusMatchesEvent, } from '@openfeature/core'; import { FlagEvaluationOptions } from '../evaluation'; import { OpenFeature } from '../open-feature'; import { Provider } from '../provider'; import { Client } from './client'; import { InternalEventEmitter } from '../events/internal/internal-event-emitter'; +import { Hook } from '../hooks'; type OpenFeatureClientOptions = { name?: string; @@ -41,7 +41,7 @@ export class OpenFeatureClient implements Client, ManageContext InternalEventEmitter, private readonly globalLogger: () => Logger, private readonly options: OpenFeatureClientOptions, - context: EvaluationContext = {} + context: EvaluationContext = {}, ) { this._context = context; } @@ -90,12 +90,12 @@ export class OpenFeatureClient implements Client, ManageContext[]): OpenFeatureClient { + addHooks(...hooks: Hook[]): OpenFeatureClient { this._hooks = [...this._hooks, ...hooks]; return this; } - getHooks(): Hook[] { + getHooks(): Hook[] { return this._hooks; } @@ -108,7 +108,7 @@ export class OpenFeatureClient implements Client, ManageContext { return (await this.getBooleanDetails(flagKey, defaultValue, context, options)).value; } @@ -117,7 +117,7 @@ export class OpenFeatureClient implements Client, ManageContext> { return this.evaluate( flagKey, @@ -125,7 +125,7 @@ export class OpenFeatureClient implements Client, ManageContext { return (await this.getStringDetails(flagKey, defaultValue, context, options)).value; } @@ -142,7 +142,7 @@ export class OpenFeatureClient implements Client, ManageContext> { return this.evaluate( flagKey, @@ -151,7 +151,7 @@ export class OpenFeatureClient implements Client, ManageContext { return (await this.getNumberDetails(flagKey, defaultValue, context, options)).value; } @@ -168,7 +168,7 @@ export class OpenFeatureClient implements Client, ManageContext> { return this.evaluate( flagKey, @@ -177,7 +177,7 @@ export class OpenFeatureClient implements Client, ManageContext { return (await this.getObjectDetails(flagKey, defaultValue, context, options)).value; } @@ -194,7 +194,7 @@ export class OpenFeatureClient implements Client, ManageContext> { return this.evaluate(flagKey, this._provider.resolveObjectEvaluation, defaultValue, 'object', context, options); } @@ -205,12 +205,12 @@ export class OpenFeatureClient implements Client, ManageContext Promise>, defaultValue: T, flagType: FlagValueType, invocationContext: EvaluationContext = {}, - options: FlagEvaluationOptions = {} + options: FlagEvaluationOptions = {}, ): Promise> { // merge global, client, and evaluation context @@ -296,7 +296,7 @@ export class OpenFeatureClient implements Client, ManageContext, - options: FlagEvaluationOptions + options: FlagEvaluationOptions, ) { // run "after" hooks sequentially for (const hook of hooks) { diff --git a/packages/server/src/evaluation/evaluation.ts b/packages/server/src/evaluation/evaluation.ts index 979d7818..70af4bfd 100644 --- a/packages/server/src/evaluation/evaluation.ts +++ b/packages/server/src/evaluation/evaluation.ts @@ -1,4 +1,5 @@ -import { EvaluationContext, EvaluationDetails, Hook, HookHints, JsonValue } from '@openfeature/core'; +import { EvaluationContext, EvaluationDetails, HookHints, JsonValue } from '@openfeature/core'; +import { Hook } from '../hooks'; export interface FlagEvaluationOptions { hooks?: Hook[]; @@ -18,7 +19,7 @@ export interface Features { flagKey: string, defaultValue: boolean, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise; /** @@ -33,7 +34,7 @@ export interface Features { flagKey: string, defaultValue: boolean, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise>; /** @@ -49,14 +50,14 @@ export interface Features { flagKey: string, defaultValue: string, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise; getStringValue( flagKey: string, defaultValue: T, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise; /** @@ -72,14 +73,14 @@ export interface Features { flagKey: string, defaultValue: string, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise>; getStringDetails( flagKey: string, defaultValue: T, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise>; /** @@ -95,14 +96,14 @@ export interface Features { flagKey: string, defaultValue: number, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise; getNumberValue( flagKey: string, defaultValue: T, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise; /** @@ -118,14 +119,14 @@ export interface Features { flagKey: string, defaultValue: number, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise>; getNumberDetails( flagKey: string, defaultValue: T, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise>; /** @@ -141,14 +142,14 @@ export interface Features { flagKey: string, defaultValue: JsonValue, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise; getObjectValue( flagKey: string, defaultValue: T, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise; /** @@ -164,13 +165,13 @@ export interface Features { flagKey: string, defaultValue: JsonValue, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise>; getObjectDetails( flagKey: string, defaultValue: T, context?: EvaluationContext, - options?: FlagEvaluationOptions + options?: FlagEvaluationOptions, ): Promise>; } diff --git a/packages/server/src/events/index.ts b/packages/server/src/events/index.ts index 4377e48f..47d1dbc6 100644 --- a/packages/server/src/events/index.ts +++ b/packages/server/src/events/index.ts @@ -1 +1 @@ -export * from './open-feature-event-emitter'; \ No newline at end of file +export * from './open-feature-event-emitter'; diff --git a/packages/server/src/hooks/hook.ts b/packages/server/src/hooks/hook.ts new file mode 100644 index 00000000..606c2ed0 --- /dev/null +++ b/packages/server/src/hooks/hook.ts @@ -0,0 +1,7 @@ +import { BaseHook, EvaluationContext, FlagValue } from '@openfeature/core'; + +export type Hook = BaseHook< + FlagValue, + Promise> | EvaluationContext | void, + Promise | void +>; diff --git a/packages/server/src/hooks/index.ts b/packages/server/src/hooks/index.ts new file mode 100644 index 00000000..1146a6d5 --- /dev/null +++ b/packages/server/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './hook'; diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index d76d3dbd..c229d16f 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -4,4 +4,5 @@ export * from './evaluation'; export * from './open-feature'; export * from './transaction-context'; export * from './events'; +export * from './hooks'; export * from '@openfeature/core'; diff --git a/packages/server/src/open-feature.ts b/packages/server/src/open-feature.ts index cdaa1793..bfcd87cd 100644 --- a/packages/server/src/open-feature.ts +++ b/packages/server/src/open-feature.ts @@ -14,6 +14,7 @@ import { } from './transaction-context'; import { Client, OpenFeatureClient } from './client'; import { OpenFeatureEventEmitter } from './events'; +import { Hook } from './hooks'; // use a symbol as a key for the global singleton const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/js-sdk/api'); @@ -24,13 +25,13 @@ type OpenFeatureGlobal = { const _globalThis = globalThis as OpenFeatureGlobal; export class OpenFeatureAPI - extends OpenFeatureCommonAPI + extends OpenFeatureCommonAPI implements ManageContext, ManageTransactionContextPropagator> { protected _events = new OpenFeatureEventEmitter(); protected _defaultProvider: Provider = NOOP_PROVIDER; protected _createEventEmitter = () => new OpenFeatureEventEmitter(); - + private _transactionContextPropagator: TransactionContextPropagator = NOOP_TRANSACTION_CONTEXT_PROPAGATOR; private constructor() { @@ -100,7 +101,7 @@ export class OpenFeatureAPI getClient( nameOrContext?: string | EvaluationContext, versionOrContext?: string | EvaluationContext, - contextOrUndefined?: EvaluationContext + contextOrUndefined?: EvaluationContext, ): Client { const name = stringOrUndefined(nameOrContext); const version = stringOrUndefined(versionOrContext); @@ -114,7 +115,7 @@ export class OpenFeatureAPI () => this.buildAndCacheEventEmitterForClient(name), () => this._logger, { name, version }, - context + context, ); } @@ -127,7 +128,7 @@ export class OpenFeatureAPI } setTransactionContextPropagator( - transactionContextPropagator: TransactionContextPropagator + transactionContextPropagator: TransactionContextPropagator, ): OpenFeatureCommonAPI { const baseMessage = 'Invalid TransactionContextPropagator, will not be set: '; if (typeof transactionContextPropagator?.getTransactionContext !== 'function') { diff --git a/packages/server/src/provider/provider.ts b/packages/server/src/provider/provider.ts index 83b23788..6e9ab0ce 100644 --- a/packages/server/src/provider/provider.ts +++ b/packages/server/src/provider/provider.ts @@ -1,4 +1,5 @@ -import { CommonProvider, EvaluationContext, Hook, JsonValue, Logger, ResolutionDetails } from '@openfeature/core'; +import { CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfeature/core'; +import { Hook } from '../hooks'; /** * Interface that providers must implement to resolve flag values for their particular @@ -22,7 +23,7 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: boolean, context: EvaluationContext, - logger: Logger + logger: Logger, ): Promise>; /** @@ -32,7 +33,7 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: string, context: EvaluationContext, - logger: Logger + logger: Logger, ): Promise>; /** @@ -42,7 +43,7 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: number, context: EvaluationContext, - logger: Logger + logger: Logger, ): Promise>; /** @@ -52,6 +53,6 @@ export interface Provider extends CommonProvider { flagKey: string, defaultValue: T, context: EvaluationContext, - logger: Logger + logger: Logger, ): Promise>; } diff --git a/packages/server/test/hooks.spec.ts b/packages/server/test/hooks.spec.ts index 849db312..4dd60dc9 100644 --- a/packages/server/test/hooks.spec.ts +++ b/packages/server/test/hooks.spec.ts @@ -167,7 +167,7 @@ describe('Hooks', () => { expect.objectContaining({ beforeRan: true, }), - expect.anything() + expect.anything(), ); }); }); @@ -252,7 +252,7 @@ describe('Hooks', () => { [invocationPropToOverwrite434]: true, [hookProp434]: true, }), - expect.anything() + expect.anything(), ); }); }); @@ -903,7 +903,7 @@ describe('Hooks', () => { return new Promise((resolve) => setTimeout(() => { resolve({ beforeRan: true }); - }, 100) + }, 100), ); }), after: jest.fn((hookContext) => { @@ -940,7 +940,7 @@ describe('Hooks', () => { return new Promise((resolve, reject) => setTimeout(() => { reject(); - }, 100) + }, 100), ); }), error: jest.fn(() => { diff --git a/packages/server/test/logger.spec.ts b/packages/server/test/logger.spec.ts index 08a539a5..93f69b49 100644 --- a/packages/server/test/logger.spec.ts +++ b/packages/server/test/logger.spec.ts @@ -1,4 +1,4 @@ -import { OpenFeature, Hook, Logger, Provider, DefaultLogger, SafeLogger } from '../src'; +import { OpenFeature, BaseHook, Logger, Provider, DefaultLogger, SafeLogger } from '../src'; class MockedLogger implements Logger { error = jest.fn(); @@ -12,7 +12,7 @@ const AFTER_HOOK_LOG_MESSAGE = 'in after hook'; const ERROR_HOOK_LOG_MESSAGE = 'in error hook'; const FINALLY_HOOK_LOG_MESSAGE = 'in finally hook'; -const MOCK_HOOK: Hook = { +const MOCK_HOOK: BaseHook = { before: jest.fn((hookContext) => hookContext.logger.info(BEFORE_HOOK_LOG_MESSAGE)), after: jest.fn((hookContext) => hookContext.logger.info(AFTER_HOOK_LOG_MESSAGE)), error: jest.fn((hookContext) => hookContext.logger.info(ERROR_HOOK_LOG_MESSAGE)), @@ -93,7 +93,7 @@ describe('Logger', () => { const safeLogger = new SafeLogger({} as Logger); expect(errorSpy).toBeCalledWith( - expect.objectContaining({ message: 'The provided logger is missing the error method.' }) + expect.objectContaining({ message: 'The provided logger is missing the error method.' }), ); // Checking the private logger expect(safeLogger['logger']).toBeInstanceOf(DefaultLogger); diff --git a/packages/shared/src/hooks/evaluation-lifecycle.ts b/packages/shared/src/hooks/evaluation-lifecycle.ts index 9bb88632..37816902 100644 --- a/packages/shared/src/hooks/evaluation-lifecycle.ts +++ b/packages/shared/src/hooks/evaluation-lifecycle.ts @@ -1,4 +1,4 @@ -import { Hook } from './hook'; +import { BaseHook } from './hook'; import { FlagValue } from '../evaluation'; export interface EvaluationLifeCycle { @@ -9,16 +9,16 @@ export interface EvaluationLifeCycle { * Hooks registered on the global API object run with all evaluations. * Hooks registered on the client run with all evaluations on that client. * @template T The type of the receiver - * @param {Hook[]} hooks A list of hooks that should always run + * @param {BaseHook[]} hooks A list of hooks that should always run * @returns {T} The receiver (this object) */ - addHooks(...hooks: Hook[]): T; + addHooks(...hooks: BaseHook[]): T; /** * Access all the hooks that are registered on this receiver. - * @returns {Hook[]} A list of the client hooks + * @returns {BaseHook[]} A list of the client hooks */ - getHooks(): Hook[]; + getHooks(): BaseHook[]; /** * Clears all the hooks that are registered on this receiver. diff --git a/packages/shared/src/hooks/hook.ts b/packages/shared/src/hooks/hook.ts index 83b83c61..fe89f150 100644 --- a/packages/shared/src/hooks/hook.ts +++ b/packages/shared/src/hooks/hook.ts @@ -1,17 +1,14 @@ import { BeforeHookContext, HookContext, HookHints } from './hooks'; -import { EvaluationContext, EvaluationDetails, FlagValue } from '../evaluation'; +import { EvaluationDetails, FlagValue } from '../evaluation'; -export interface Hook { +export interface BaseHook { /** * Runs before flag values are resolved from the provider. * If an EvaluationContext is returned, it will be merged with the pre-existing EvaluationContext. * @param hookContext * @param hookHints */ - before?( - hookContext: BeforeHookContext, - hookHints?: HookHints - ): Promise | EvaluationContext | void; + before?(hookContext: BeforeHookContext, hookHints?: HookHints): BeforeHookReturn; /** * Runs after flag values are successfully resolved from the provider. @@ -22,8 +19,8 @@ export interface Hook { after?( hookContext: Readonly>, evaluationDetails: EvaluationDetails, - hookHints?: HookHints - ): Promise | void; + hookHints?: HookHints, + ): HooksReturn; /** * Runs in the event of an unhandled error or promise rejection during flag resolution, or any attached hooks. @@ -31,7 +28,7 @@ export interface Hook { * @param error * @param hookHints */ - error?(hookContext: Readonly>, error: unknown, hookHints?: HookHints): Promise | void; + error?(hookContext: Readonly>, error: unknown, hookHints?: HookHints): HooksReturn; /** * Runs after all other hook stages, regardless of success or error. @@ -39,5 +36,5 @@ export interface Hook { * @param hookContext * @param hookHints */ - finally?(hookContext: Readonly>, hookHints?: HookHints): Promise | void; + finally?(hookContext: Readonly>, hookHints?: HookHints): HooksReturn; } diff --git a/packages/shared/src/open-feature.ts b/packages/shared/src/open-feature.ts index dbf27452..f2d2b3e5 100644 --- a/packages/shared/src/open-feature.ts +++ b/packages/shared/src/open-feature.ts @@ -1,22 +1,22 @@ import { GeneralError } from './errors'; -import { EvaluationContext, FlagValue } from './evaluation'; +import { EvaluationContext } from './evaluation'; import { EventDetails, EventHandler, Eventing, ProviderEvents, statusMatchesEvent } from './events'; import { GenericEventEmitter } from './events'; import { isDefined } from './filter'; -import { EvaluationLifeCycle, Hook } from './hooks'; +import { EvaluationLifeCycle, BaseHook } from './hooks'; import { DefaultLogger, Logger, ManageLogger, SafeLogger } from './logger'; import { CommonProvider, ProviderMetadata, ProviderStatus } from './provider'; import { objectOrUndefined, stringOrUndefined } from './type-guards'; import { Paradigm } from './types'; -export abstract class OpenFeatureCommonAPI

+export abstract class OpenFeatureCommonAPI

implements Eventing, EvaluationLifeCycle>, ManageLogger> { protected abstract _createEventEmitter(): GenericEventEmitter; protected abstract _defaultProvider: P; protected abstract readonly _events: GenericEventEmitter; - protected _hooks: Hook[] = []; + protected _hooks: H[] = []; protected _context: EvaluationContext = {}; protected _logger: Logger = new DefaultLogger(); @@ -30,12 +30,12 @@ export abstract class OpenFeatureCommonAPI

[]): this { + addHooks(...hooks: H[]): this { this._hooks = [...this._hooks, ...hooks]; return this; } - getHooks(): Hook[] { + getHooks(): H[] { return this._hooks; } @@ -184,7 +184,7 @@ export abstract class OpenFeatureCommonAPI

(ProviderEvents).forEach((eventType) => - clientProvider.events?.addHandler(eventType, async (details) => { - newEmitter.emit(eventType, { ...details, clientName: name, providerName: clientProvider.metadata.name }); - }) + Object.values(ProviderEvents).forEach( + (eventType) => + clientProvider.events?.addHandler(eventType, async (details) => { + newEmitter.emit(eventType, { ...details, clientName: name, providerName: clientProvider.metadata.name }); + }), ); return newEmitter; @@ -280,7 +281,7 @@ export abstract class OpenFeatureCommonAPI