Skip to content

Commit 122fe7b

Browse files
committed
fix(context): clear binding cache upon scope or value getter changes
1 parent 705dcd5 commit 122fe7b

File tree

2 files changed

+76
-2
lines changed

2 files changed

+76
-2
lines changed

packages/context/src/__tests__/unit/binding.unit.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
import {expect} from '@loopback/testlab';
6+
import {expect, sinon, SinonSpy} from '@loopback/testlab';
77
import {
88
Binding,
99
BindingScope,
@@ -214,6 +214,68 @@ describe('Binding', () => {
214214
});
215215
});
216216

217+
describe('cache', () => {
218+
let spy: SinonSpy;
219+
beforeEach(() => {
220+
spy = sinon.spy();
221+
});
222+
223+
it('clears cache if scope changes', () => {
224+
const indexBinding = ctx
225+
.bind<number>('index')
226+
.toDynamicValue(spy)
227+
.inScope(BindingScope.SINGLETON);
228+
229+
ctx.getSync(indexBinding.key);
230+
sinon.assert.calledOnce(spy);
231+
spy.resetHistory();
232+
233+
// Singleton
234+
ctx.getSync(indexBinding.key);
235+
sinon.assert.notCalled(spy);
236+
spy.resetHistory();
237+
238+
indexBinding.inScope(BindingScope.CONTEXT);
239+
ctx.getSync(indexBinding.key);
240+
sinon.assert.calledOnce(spy);
241+
});
242+
243+
it('clears cache if _getValue changes', () => {
244+
const providerSpy = sinon.spy();
245+
class IndexProvider implements Provider<number> {
246+
value() {
247+
return providerSpy();
248+
}
249+
}
250+
const indexBinding = ctx
251+
.bind<number>('index')
252+
.toDynamicValue(spy)
253+
.inScope(BindingScope.SINGLETON);
254+
255+
ctx.getSync(indexBinding.key);
256+
sinon.assert.calledOnce(spy);
257+
spy.resetHistory();
258+
259+
// Singleton
260+
ctx.getSync(indexBinding.key);
261+
sinon.assert.notCalled(spy);
262+
spy.resetHistory();
263+
264+
// Now change the value getter
265+
indexBinding.toProvider(IndexProvider);
266+
ctx.getSync(indexBinding.key);
267+
sinon.assert.notCalled(spy);
268+
sinon.assert.calledOnce(providerSpy);
269+
spy.resetHistory();
270+
providerSpy.resetHistory();
271+
272+
// Singleton
273+
ctx.getSync(indexBinding.key);
274+
sinon.assert.notCalled(spy);
275+
sinon.assert.notCalled(providerSpy);
276+
});
277+
});
278+
217279
describe('toJSON()', () => {
218280
it('converts a keyed binding to plain JSON object', () => {
219281
const json = binding.toJSON();

packages/context/src/binding.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,15 @@ export class Binding<T = BoundValue> {
211211
});
212212
}
213213

214+
/**
215+
* Clear the cache
216+
*/
217+
private _clearCache() {
218+
if (!this._cache) return;
219+
// WeakMap does not have a `clear` method
220+
this._cache = new WeakMap();
221+
}
222+
214223
/**
215224
* This is an internal function optimized for performance.
216225
* Users should use `@inject(key)` or `ctx.get(key)` instead.
@@ -346,6 +355,7 @@ export class Binding<T = BoundValue> {
346355
* @param scope Binding scope
347356
*/
348357
inScope(scope: BindingScope): this {
358+
if (this._scope !== scope) this._clearCache();
349359
this._scope = scope;
350360
return this;
351361
}
@@ -357,7 +367,7 @@ export class Binding<T = BoundValue> {
357367
*/
358368
applyDefaultScope(scope: BindingScope): this {
359369
if (!this._scope) {
360-
this._scope = scope;
370+
this.inScope(scope);
361371
}
362372
return this;
363373
}
@@ -367,6 +377,8 @@ export class Binding<T = BoundValue> {
367377
* @param getValue getValue function
368378
*/
369379
private _setValueGetter(getValue: ValueGetter<T>) {
380+
// Clear the cache
381+
this._clearCache();
370382
this._getValue = getValue;
371383
}
372384

0 commit comments

Comments
 (0)