@@ -12,8 +12,15 @@ import type { LoadSubsetOptions } from "../types.js"
1212 * Tracks what data has been loaded and avoids redundant calls by applying
1313 * subset logic to predicates.
1414 *
15+ * @param opts - The options for the DeduplicatedLoadSubset
16+ * @param opts.loadSubset - The underlying loadSubset function to wrap
17+ * @param opts.onDeduplicate - An optional callback function that is invoked when a loadSubset call is deduplicated.
18+ * If the call is deduplicated because the requested data is being loaded by an inflight request,
19+ * then this callback is invoked when the inflight request completes successfully and the data is fully loaded.
20+ * This callback is useful if you need to track rows per query, in which case you can't ignore deduplicated calls
21+ * because you need to know which rows were loaded for each query.
1522 * @example
16- * const dedupe = new DeduplicatedLoadSubset(myLoadSubset)
23+ * const dedupe = new DeduplicatedLoadSubset({ loadSubset: myLoadSubset, onDeduplicate: (opts) => console.log(`Call was deduplicated:`, opts) } )
1724 *
1825 * // First call - fetches data
1926 * await dedupe.loadSubset({ where: gt(ref('age'), val(10)) })
@@ -30,6 +37,11 @@ export class DeduplicatedLoadSubset {
3037 options : LoadSubsetOptions
3138 ) => true | Promise < void >
3239
40+ // An optional callback function that is invoked when a loadSubset call is deduplicated.
41+ private readonly onDeduplicate :
42+ | ( ( options : LoadSubsetOptions ) => void )
43+ | undefined
44+
3345 // Combined where predicate for all unlimited calls (no limit)
3446 private unlimitedWhere : BasicExpression < boolean > | undefined = undefined
3547
@@ -52,10 +64,12 @@ export class DeduplicatedLoadSubset {
5264 // check if their captured generation matches before updating tracking state
5365 private generation = 0
5466
55- constructor (
67+ constructor ( opts : {
5668 loadSubset : ( options : LoadSubsetOptions ) => true | Promise < void >
57- ) {
58- this . _loadSubset = loadSubset
69+ onDeduplicate ?: ( options : LoadSubsetOptions ) => void
70+ } ) {
71+ this . _loadSubset = opts . loadSubset
72+ this . onDeduplicate = opts . onDeduplicate
5973 }
6074
6175 /**
@@ -71,13 +85,15 @@ export class DeduplicatedLoadSubset {
7185 loadSubset = ( options : LoadSubsetOptions ) : true | Promise < void > => {
7286 // If we've loaded all data, everything is covered
7387 if ( this . hasLoadedAllData ) {
88+ this . onDeduplicate ?.( options )
7489 return true
7590 }
7691
7792 // Check against unlimited combined predicate
7893 // If we've loaded all data matching a where clause, we don't need to refetch subsets
7994 if ( this . unlimitedWhere !== undefined && options . where !== undefined ) {
8095 if ( isWhereSubset ( options . where , this . unlimitedWhere ) ) {
96+ this . onDeduplicate ?.( options )
8197 return true // Data already loaded via unlimited call
8298 }
8399 }
@@ -89,6 +105,7 @@ export class DeduplicatedLoadSubset {
89105 )
90106
91107 if ( alreadyLoaded ) {
108+ this . onDeduplicate ?.( options )
92109 return true // Already loaded
93110 }
94111 }
@@ -103,7 +120,10 @@ export class DeduplicatedLoadSubset {
103120 // An in-flight call will load data that covers this request
104121 // Return the same promise so this caller waits for the data to load
105122 // The in-flight promise already handles tracking updates when it completes
106- return matchingInflight . promise
123+ const prom = matchingInflight . promise
124+ // Call `onDeduplicate` when the inflight request has loaded the data
125+ prom . then ( ( ) => this . onDeduplicate ?.( options ) ) . catch ( ) // ignore errors
126+ return prom
107127 }
108128
109129 // Not fully covered by existing data
0 commit comments