From 7cf0d6ddbf572cc2d269215bb7e90ebd214016bc Mon Sep 17 00:00:00 2001 From: Paulo Date: Thu, 25 Jul 2024 10:32:47 +0200 Subject: [PATCH 1/3] Cached maps can return values before any subscription --- .../core/src/mapFacets/mapFacetArrayCached.ts | 18 ++++++- .../src/mapFacets/mapFacetArrayLightweight.ts | 6 +-- .../src/mapFacets/mapFacetSingleCached.ts | 21 ++++++-- .../mapFacets/mapFacetSingleLightweight.ts | 6 +-- .../core/src/mapFacets/mapFacets.spec.ts | 52 +++++++++++++++++-- 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts b/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts index 4ef8eaab..27c737c4 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts @@ -7,9 +7,25 @@ export function mapFacetArrayCached( fn: (...value: unknown[]) => M | NoValue, equalityCheck?: EqualityCheck, ): Facet { - return createFacet({ + const cachedFacet = createFacet({ // pass the equalityCheck to the mapIntoObserveArray to prevent even triggering the observable startSubscription: mapIntoObserveArray(facets, fn, equalityCheck), initialValue: NO_VALUE, }) + + return { + get: () => { + const cachedValue = cachedFacet.get() + if (cachedValue !== NO_VALUE) return cachedValue + + const dependencyValues = facets.map((facet) => facet.get()) + const hasAllValues = dependencyValues.reduce((acc, value) => acc && value !== NO_VALUE, true) + if (!hasAllValues) return NO_VALUE + + const mappedValue = fn(...dependencyValues) + if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue) + return mappedValue + }, + observe: cachedFacet.observe, + } } diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts b/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts index e5b26b52..845ccba1 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts @@ -8,11 +8,11 @@ export function mapFacetArrayLightweight( ): Facet { return { get: () => { - const values = facets.map((facet) => facet.get()) - const hasAllValues = values.reduce((acc, value) => acc && value !== NO_VALUE, true) + const dependencyValues = facets.map((facet) => facet.get()) + const hasAllValues = dependencyValues.reduce((acc, value) => acc && value !== NO_VALUE, true) if (!hasAllValues) return NO_VALUE - return fn(...values) + return fn(...dependencyValues) }, observe: mapIntoObserveArray(facets, fn, equalityCheck), } diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts b/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts index 15bc8673..aacbf841 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts @@ -3,13 +3,28 @@ import { mapIntoObserveSingle } from './mapIntoObserveSingle' import { createFacet } from '../facet' export function mapFacetSingleCached( - facets: Facet, + facet: Facet, fn: (value: T) => M | NoValue, equalityCheck?: EqualityCheck, ): Facet { - return createFacet({ + const cachedFacet = createFacet({ // pass the equalityCheck to the mapIntoObserveSingle to prevent even triggering the observable - startSubscription: mapIntoObserveSingle(facets, fn, equalityCheck), + startSubscription: mapIntoObserveSingle(facet, fn, equalityCheck), initialValue: NO_VALUE, }) + + return { + get: () => { + const cachedValue = cachedFacet.get() + if (cachedValue !== NO_VALUE) return cachedValue + + const dependencyValue = facet.get() + if (dependencyValue === NO_VALUE) return NO_VALUE + + const mappedValue = fn(dependencyValue) + if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue) + return mappedValue + }, + observe: cachedFacet.observe, + } } diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts b/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts index 3b0d88b4..d313f5ef 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts @@ -8,10 +8,10 @@ export function mapFacetSingleLightweight( ): Facet { return { get: () => { - const value = facet.get() - if (value === NO_VALUE) return NO_VALUE + const dependencyValue = facet.get() + if (dependencyValue === NO_VALUE) return NO_VALUE - return fn(value) + return fn(dependencyValue) }, observe: mapIntoObserveSingle(facet, fn, equalityCheck), diff --git a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts index be78268e..fb3bcd66 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts @@ -29,22 +29,66 @@ describe('mapFacetsCached', () => { expect(mapFunction).toHaveBeenCalledTimes(1) }) - it('gets NO_VALUE as a value from a single source before any subscription', () => { + it('gets NO_VALUE as a value from a single source if it also has NO_VALUE', () => { const mapFunction = jest.fn().mockReturnValue('dummy') - const sourceFacet = createFacet({ initialValue: 'initial value' }) + const sourceFacet = createFacet({ initialValue: NO_VALUE }) const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false) expect(mapFacet.get()).toBe(NO_VALUE) }) - it('gets NO_VALUE as a value from multiple sources before any subscription', () => { + it('gets NO_VALUE as a value from multiple sources if they also have NO_VALUE', () => { const mapFunction = jest.fn().mockReturnValue('dummy') const sourceAFacet = createFacet({ initialValue: 'initial value' }) - const sourceBFacet = createFacet({ initialValue: 'initial value' }) + const sourceBFacet = createFacet({ initialValue: NO_VALUE }) const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false) expect(mapFacet.get()).toBe(NO_VALUE) }) + + it('can get the mapped value from a single source before any subscription', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + }) + + it('can get the mapped value from multiple sources before any subscription', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceAFacet = createFacet({ initialValue: 'initial value' }) + const sourceBFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + }) + + it('caches calls to the mapFunction though a get call before any subscription, given a single source', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).toHaveBeenCalledTimes(1) + + mapFunction.mockClear() + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).not.toHaveBeenCalled() + }) + + it('caches calls to the mapFunction though a get call before any subscription, given multiple sources', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceAFacet = createFacet({ initialValue: 'initial value' }) + const sourceBFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).toHaveBeenCalledTimes(1) + + mapFunction.mockClear() + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).not.toHaveBeenCalled() + }) }) describe('mapFacetsLightweight', () => { From 17cf35bcf46f85b862cf632314df18d73960301e Mon Sep 17 00:00:00 2001 From: Paulo Ragonha Date: Thu, 25 Jul 2024 10:51:49 +0200 Subject: [PATCH 2/3] Update packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts Co-authored-by: Marlon --- packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts index fb3bcd66..711cb697 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts @@ -63,7 +63,7 @@ describe('mapFacetsCached', () => { expect(mapFacet.get()).toBe('dummy') }) - it('caches calls to the mapFunction though a get call before any subscription, given a single source', () => { + it('caches calls to the mapFunction through a get call before any subscription, given a single source', () => { const mapFunction = jest.fn().mockReturnValue('dummy') const sourceFacet = createFacet({ initialValue: 'initial value' }) const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false) From a149228e10db4f8f00f8a9d7ee3c377a7ee26f50 Mon Sep 17 00:00:00 2001 From: Marlon Date: Thu, 25 Jul 2024 11:05:21 +0200 Subject: [PATCH 3/3] Update packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts --- packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts index 711cb697..12882c24 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts @@ -76,7 +76,7 @@ describe('mapFacetsCached', () => { expect(mapFunction).not.toHaveBeenCalled() }) - it('caches calls to the mapFunction though a get call before any subscription, given multiple sources', () => { + it('caches calls to the mapFunction through a get call before any subscription, given multiple sources', () => { const mapFunction = jest.fn().mockReturnValue('dummy') const sourceAFacet = createFacet({ initialValue: 'initial value' }) const sourceBFacet = createFacet({ initialValue: 'initial value' })