diff --git a/packages/server/src/client/internal/open-feature-client.ts b/packages/server/src/client/internal/open-feature-client.ts index 01120f693..7dcb13ef8 100644 --- a/packages/server/src/client/internal/open-feature-client.ts +++ b/packages/server/src/client/internal/open-feature-client.ts @@ -332,7 +332,7 @@ export class OpenFeatureClient implements Client { mergedContext: EvaluationContext, options: FlagEvaluationOptions, ) { - let accumulatedContext = mergedContext; + const accumulatedContext = mergedContext; for (const [index, hook] of hooks.entries()) { const hookContextIndex = hooks.length - 1 - index; // reverse index for before hooks @@ -343,10 +343,7 @@ export class OpenFeatureClient implements Client { const hookResult = await hook?.before?.(hookContext, Object.freeze(options.hookHints)); if (hookResult) { - accumulatedContext = { - ...accumulatedContext, - ...hookResult, - }; + Object.assign(accumulatedContext, hookResult); for (let i = 0; i < hooks.length; i++) { Object.assign(hookContexts[hookContextIndex].context, accumulatedContext); diff --git a/packages/server/test/client.spec.ts b/packages/server/test/client.spec.ts index b1db27412..7dac9335b 100644 --- a/packages/server/test/client.spec.ts +++ b/packages/server/test/client.spec.ts @@ -811,6 +811,40 @@ describe('OpenFeatureClient', () => { client.setContext({ [KEY]: VAL }); expect(client.getContext()[KEY]).toEqual(VAL); }); + + it('context object is reference stable between hook and evaluation calls', async () => { + let hookContextRef; + const contextMap = new WeakMap(); + const contextStabilityProvider = { + metadata: { + name: 'evaluation-context', + }, + hooks: [ + { + before: jest.fn((hookContext: HookContext) => { + contextMap.set(hookContext.context, hookContext); + return hookContext.context; + }) + } + ], + resolveBooleanEvaluation: jest.fn((_flagKey, _defaultValue, context): Promise> => { + // We expect that the context object reference is the same as that captured in the hook + hookContextRef = contextMap.get(context); + return Promise.resolve({ + value: true, + }); + }), + } as unknown as Provider; + + await OpenFeature.setProviderAndWait(contextStabilityProvider); + const client = OpenFeature.getClient(); + + const context = { data: 1, value: '2' }; + await client.getBooleanValue('some-other-flag', false, context); + expect(contextStabilityProvider.resolveBooleanEvaluation).toHaveBeenCalled(); + expect(contextStabilityProvider.hooks?.[0].before).toHaveBeenCalled(); + expect(hookContextRef).toBeDefined(); + }); }); });