Skip to content

Commit 2d98811

Browse files
committed
add createDeduplicatedLoadSubset function
1 parent 0321118 commit 2d98811

File tree

3 files changed

+409
-0
lines changed

3 files changed

+409
-0
lines changed

packages/db/src/query/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,5 @@ export {
6969
intersectPredicates,
7070
unionPredicates,
7171
} from "./predicate-utils.js"
72+
73+
export { createDeduplicatedLoadSubset } from "./subset-dedupe.js"
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {
2+
isPredicateSubset,
3+
isWhereSubset,
4+
unionWherePredicates,
5+
} from "./predicate-utils.js"
6+
import type { BasicExpression } from "./ir.js"
7+
import type { LoadSubsetOptions } from "../types.js"
8+
9+
/**
10+
* Creates a deduplicated wrapper around a loadSubset function.
11+
* Tracks what data has been loaded and avoids redundant calls.
12+
*
13+
* @param loadSubset - The underlying loadSubset function to wrap
14+
* @returns A wrapped function that deduplicates calls based on loaded predicates
15+
*
16+
* @example
17+
* const deduplicatedLoadSubset = createDeduplicatedLoadSubset(myLoadSubset)
18+
*
19+
* // First call - fetches data
20+
* await deduplicatedLoadSubset({ where: gt(ref('age'), val(10)) })
21+
*
22+
* // Second call - subset of first, returns true immediately
23+
* await deduplicatedLoadSubset({ where: gt(ref('age'), val(20)) })
24+
*/
25+
export function createDeduplicatedLoadSubset(
26+
loadSubset: (options: LoadSubsetOptions) => true | Promise<void>
27+
): (options: LoadSubsetOptions) => true | Promise<void> {
28+
// Combined where predicate for all unlimited calls (no limit)
29+
let unlimitedWhere: BasicExpression<boolean> | undefined = undefined
30+
31+
// Flag to track if we've loaded all data (unlimited call with no where clause)
32+
let hasLoadedAllData = false
33+
34+
// List of all limited calls (with limit, possibly with orderBy)
35+
const limitedCalls: Array<LoadSubsetOptions> = []
36+
37+
return (options: LoadSubsetOptions) => {
38+
// If we've loaded all data, everything is covered
39+
if (hasLoadedAllData) {
40+
return true
41+
}
42+
43+
// Check against unlimited combined predicate
44+
// If we've loaded all data matching a where clause, we don't need to refetch subsets
45+
if (unlimitedWhere !== undefined && options.where !== undefined) {
46+
if (isWhereSubset(options.where, unlimitedWhere)) {
47+
return true // Data already loaded via unlimited call
48+
}
49+
}
50+
51+
// Check against limited calls
52+
if (options.limit !== undefined) {
53+
const alreadyLoaded = limitedCalls.some((loaded) =>
54+
isPredicateSubset(options, loaded)
55+
)
56+
57+
if (alreadyLoaded) {
58+
return true // Already loaded
59+
}
60+
}
61+
62+
// Not covered by existing data - call underlying loadSubset
63+
const resultPromise = loadSubset(options)
64+
65+
// Handle both sync (true) and async (Promise<void>) return values
66+
if (resultPromise === true) {
67+
// Sync return - update tracking synchronously
68+
updateTracking(options)
69+
return true
70+
} else {
71+
// Async return - update tracking after promise resolves
72+
return resultPromise.then((result) => {
73+
updateTracking(options)
74+
return result
75+
})
76+
}
77+
}
78+
79+
function updateTracking(options: LoadSubsetOptions) {
80+
// Update tracking based on whether this was a limited or unlimited call
81+
if (options.limit === undefined) {
82+
// Unlimited call - update combined where predicate
83+
// We ignore orderBy for unlimited calls as mentioned in requirements
84+
if (options.where === undefined) {
85+
// No where clause = all data loaded
86+
hasLoadedAllData = true
87+
unlimitedWhere = undefined
88+
} else if (unlimitedWhere === undefined) {
89+
unlimitedWhere = options.where
90+
} else {
91+
unlimitedWhere = unionWherePredicates([unlimitedWhere, options.where])
92+
}
93+
} else {
94+
// Limited call - add to list for future subset checks
95+
limitedCalls.push(options)
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)