@@ -44,6 +44,7 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
4444 // reference to reduce the amount of O(n) operations per query to 1
4545 const touchedQueries = new Set < Array < string > > ( ) ;
4646
47+ // collect all entities that used this entity as a entry in on of their relation fields
4748 const touchedRelationParents = new Set < DecodedEntitiesCacheEntry > ( ) ;
4849
4950 // loop over all changed entities and update the cache
@@ -55,6 +56,7 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
5556 const cacheEntry = decodedEntitiesCache . get ( typeName ) ;
5657 if ( ! cacheEntry ) continue ;
5758
59+ const oldDecodedEntry = cacheEntry . entities . get ( entityId ) ;
5860 const relations = getEntityRelations ( entity , cacheEntry . type , doc ) ;
5961 const decoded = cacheEntry . decoder ( {
6062 ...entity ,
@@ -63,6 +65,51 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
6365 } ) ;
6466 cacheEntry . entities . set ( entityId , decoded ) ;
6567
68+ if ( oldDecodedEntry ) {
69+ // collect all the Ids for relation entries that don't exist in the `decoded` entry, but did in the `oldDecodedEntry`
70+ const deletedRelationIds = new Set < string > ( ) ;
71+ for ( const [ fieldName , value ] of Object . entries ( oldDecodedEntry ) ) {
72+ if ( Array . isArray ( value ) ) {
73+ for ( const relationEntity of value ) {
74+ // @ts -expect-error decoded is a valid object
75+ if ( ! decoded [ fieldName ] ?. includes ( relationEntity . id ) ) {
76+ deletedRelationIds . add ( relationEntity . id ) ;
77+ }
78+ }
79+ }
80+ }
81+
82+ // it's fine to remove all of them since they are re-added below
83+ for ( const deletedRelationId of deletedRelationIds ) {
84+ const deletedRelationEntry = entityRelationParentsMap . get ( deletedRelationId ) ;
85+ if ( deletedRelationEntry ) {
86+ deletedRelationEntry . set ( cacheEntry , ( deletedRelationEntry . get ( cacheEntry ) ?? 0 ) - 1 ) ;
87+ if ( deletedRelationEntry . get ( cacheEntry ) === 0 ) {
88+ deletedRelationEntry . delete ( cacheEntry ) ;
89+ }
90+ if ( deletedRelationEntry . size === 0 ) {
91+ entityRelationParentsMap . delete ( deletedRelationId ) ;
92+ }
93+ }
94+ }
95+ }
96+
97+ // @ts -expect-error decoded is a valid object
98+ for ( const [ key , value ] of Object . entries ( decoded ) ) {
99+ if ( Array . isArray ( value ) ) {
100+ for ( const relationEntity of value ) {
101+ let relationParentEntry = entityRelationParentsMap . get ( relationEntity . id ) ;
102+ if ( relationParentEntry ) {
103+ relationParentEntry . set ( cacheEntry , ( relationParentEntry . get ( cacheEntry ) ?? 0 ) + 1 ) ;
104+ } else {
105+ relationParentEntry = new Map ( ) ;
106+ entityRelationParentsMap . set ( relationEntity . id , relationParentEntry ) ;
107+ relationParentEntry . set ( cacheEntry , 1 ) ;
108+ }
109+ }
110+ }
111+ }
112+
66113 const query = cacheEntry . queries . get ( 'all' ) ;
67114 if ( query ) {
68115 const index = query . data . findIndex ( ( entity ) => entity . id === entityId ) ;
@@ -72,31 +119,17 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
72119 query . data . push ( decoded ) ;
73120 }
74121 touchedQueries . add ( [ typeName , 'all' ] ) ;
75-
76- // @ts -expect-error decoded is a valid object
77- for ( const [ key , value ] of Object . entries ( decoded ) ) {
78- if ( Array . isArray ( value ) ) {
79- for ( const relationEntity of value ) {
80- let relationParentEntry = entityRelationParentsMap . get ( relationEntity . id ) ;
81- if ( ! relationParentEntry ) {
82- relationParentEntry = [ ] ;
83- entityRelationParentsMap . set ( relationEntity . id , relationParentEntry ) ;
84- }
85-
86- relationParentEntry . push ( cacheEntry ) ;
87- }
88- }
89- }
90122 }
91123
92124 entityTypes . add ( typeName ) ;
93125
94- // gather all the decodedEntitiesCacheEntries
126+ // gather all the decodedEntitiesCacheEntries that have a relation to this entity to
127+ // invoke their query listeners below
95128 if ( entityRelationParentsMap . has ( entityId ) ) {
96129 const decodedEntitiesCacheEntries = entityRelationParentsMap . get ( entityId ) ;
97130 if ( ! decodedEntitiesCacheEntries ) return ;
98131
99- for ( const entry of decodedEntitiesCacheEntries ) {
132+ for ( const [ entry ] of decodedEntitiesCacheEntries ) {
100133 touchedRelationParents . add ( entry ) ;
101134 }
102135 }
@@ -121,17 +154,20 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
121154 }
122155 }
123156
124- // gather all the queries of impacted parent relation queries
157+ // gather all the queries of impacted parent relation queries and then remove the cacheEntry
125158 if ( entityRelationParentsMap . has ( entityId ) ) {
126159 const decodedEntitiesCacheEntries = entityRelationParentsMap . get ( entityId ) ;
127160 if ( ! decodedEntitiesCacheEntries ) return ;
128161
129- for ( const entry of decodedEntitiesCacheEntries ) {
162+ for ( const [ entry ] of decodedEntitiesCacheEntries ) {
130163 touchedRelationParents . add ( entry ) ;
131164 }
165+
166+ entityRelationParentsMap . delete ( entityId ) ;
132167 }
133168 }
134169
170+ // update the queries affected queries
135171 for ( const [ typeName , queryKey ] of touchedQueries ) {
136172 const cacheEntry = decodedEntitiesCache . get ( typeName ) ;
137173 if ( ! cacheEntry ) continue ;
@@ -155,7 +191,6 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
155191 }
156192
157193 // trigger all the listeners of the parent relation queries
158- // TODO: align with the touchedQueries to avoid unnecessary trigger calls
159194 for ( const decodedEntitiesCacheEntry of touchedRelationParents ) {
160195 decodedEntitiesCacheEntry . isInvalidated = true ;
161196 for ( const query of decodedEntitiesCacheEntry . queries . values ( ) ) {
@@ -278,12 +313,13 @@ export function subscribeToFindMany<const S extends AnyNoContext>(
278313 if ( Array . isArray ( value ) ) {
279314 for ( const relationEntity of value ) {
280315 let relationParentEntry = entityRelationParentsMap . get ( relationEntity . id ) ;
281- if ( ! relationParentEntry ) {
282- relationParentEntry = [ ] ;
316+ if ( relationParentEntry ) {
317+ relationParentEntry . set ( cacheEntry , ( relationParentEntry . get ( cacheEntry ) ?? 0 ) + 1 ) ;
318+ } else {
319+ relationParentEntry = new Map ( ) ;
283320 entityRelationParentsMap . set ( relationEntity . id , relationParentEntry ) ;
321+ relationParentEntry . set ( cacheEntry , 1 ) ;
284322 }
285-
286- relationParentEntry . push ( cacheEntry ) ;
287323 }
288324 }
289325 }
@@ -317,16 +353,12 @@ export function subscribeToFindMany<const S extends AnyNoContext>(
317353 // if the last query is removed, cleanup the entityRelationParentsMap and remove the decodedEntitiesCacheEntry
318354 if ( cacheEntry . queries . size === 0 ) {
319355 entityRelationParentsMap . forEach ( ( relationCacheEntries , key ) => {
320- for ( const relationCacheEntry of relationCacheEntries ) {
321- if ( relationCacheEntry === cacheEntry ) {
322- entityRelationParentsMap . set (
323- key ,
324- relationCacheEntries . filter ( ( entry ) => entry !== cacheEntry ) ,
325- ) ;
356+ for ( const [ relationCacheEntry , counter ] of relationCacheEntries ) {
357+ if ( relationCacheEntry === cacheEntry && counter === 0 ) {
358+ relationCacheEntries . delete ( cacheEntry ) ;
326359 }
327360 }
328- const updatedRelationCacheEntries = entityRelationParentsMap . get ( key ) ;
329- if ( updatedRelationCacheEntries && updatedRelationCacheEntries . length === 0 ) {
361+ if ( relationCacheEntries . size === 0 ) {
330362 entityRelationParentsMap . delete ( key ) ;
331363 }
332364 } ) ;
0 commit comments