Skip to content

Commit 42865ff

Browse files
committed
feat: expose useQueryTracking
1 parent 0bc5706 commit 42865ff

File tree

5 files changed

+200
-180
lines changed

5 files changed

+200
-180
lines changed

packages/vue/src/api.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ export function createCollectionApi<
332332
}) {
333333
return createQuery<TCollection, TCollectionDefaults, TSchema, any, WrappedItem<TCollection, TCollectionDefaults, TSchema> | null | Array<WrappedItem<TCollection, TCollectionDefaults, TSchema>>>({
334334
store,
335-
collection,
336335
fetchMethod: (options, meta) => toValue(type) === 'first'
337336
? findFirst({ store, collection, findOptions: options!, meta }).then(r => r.result)
338337
: findMany({ store, collection, findOptions: options, meta }).then(r => r.result),
@@ -341,7 +340,6 @@ export function createCollectionApi<
341340
: peekMany({ store, collection, findOptions: options, meta, force: true }).result,
342341
defaultValue: () => toValue(type) === 'first' ? null : [],
343342
options: boundOptionsGetter,
344-
name: type,
345343
})
346344
}
347345

packages/vue/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ export type {
2727
VueStore,
2828
} from './store'
2929

30+
export {
31+
useQueryTracking,
32+
} from './tracking'
33+
34+
export type {
35+
UseQueryTrackingOptions,
36+
} from './tracking'
37+
3038
export {
3139
addCollectionRelations,
3240
defineCollection,

packages/vue/src/query.ts

Lines changed: 17 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { Collection, CollectionDefaults, CustomHookMeta, FindOptions, HookMetaQueryTracking, HybridPromise, ResolvedCollection, StoreSchema, WrappedItemBase } from '@rstore/shared'
1+
import type { Collection, CollectionDefaults, CustomHookMeta, FindOptions, HookMetaQueryTracking, HybridPromise, StoreSchema } from '@rstore/shared'
22
import type { MaybeRefOrGetter, Ref } from 'vue'
33
import type { VueStore } from './store'
4-
import { tryOnScopeDispose } from '@vueuse/core'
5-
import { computed, nextTick, ref, shallowRef, toValue, watch } from 'vue'
4+
import { computed, ref, shallowRef, toValue, watch } from 'vue'
5+
import { useQueryTracking } from './tracking'
66

77
export interface VueQueryReturn<
88
TCollection extends Collection,
@@ -29,12 +29,10 @@ export interface VueCreateQueryOptions<
2929
TResult,
3030
> {
3131
store: VueStore<TSchema, TCollectionDefaults>
32-
collection: ResolvedCollection<TCollection, TCollectionDefaults, TSchema>
3332
fetchMethod: (options: TOptions | undefined, meta: CustomHookMeta) => Promise<TResult>
3433
cacheMethod: (options: TOptions | undefined, meta: CustomHookMeta) => TResult
3534
defaultValue: MaybeRefOrGetter<TResult>
3635
options?: MaybeRefOrGetter<TOptions | undefined | { enabled: boolean }>
37-
name: MaybeRefOrGetter<string>
3836
}
3937

4038
/**
@@ -52,11 +50,7 @@ export function createQuery<
5250
cacheMethod,
5351
defaultValue,
5452
options,
55-
collection,
56-
name,
5753
}: VueCreateQueryOptions<TCollection, TCollectionDefaults, TSchema, TOptions, TResult>): HybridPromise<VueQueryReturn<TCollection, TCollectionDefaults, TSchema, TResult>> {
58-
const trackingQueryId = `${collection.name}:${toValue(name)}:${crypto.randomUUID()}`
59-
6054
function getOptions(): TOptions | undefined {
6155
const result = toValue(options)
6256
return typeof result === 'object' && 'enabled' in result && result.enabled === false ? undefined : result as TOptions
@@ -69,41 +63,34 @@ export function createQuery<
6963

7064
let fetchPolicy = store.$getFetchPolicy(getOptions()?.fetchPolicy)
7165

72-
let queryTracking: HookMetaQueryTracking | null = null
73-
const queryTrackingEnabled = !store.$isServer && (getOptions()?.experimentalGarbageCollection ?? store.$experimentalGarbageCollection)
66+
const queryTrackingEnabled = !store.$isServer && fetchPolicy !== 'no-cache' && (getOptions()?.experimentalGarbageCollection ?? store.$experimentalGarbageCollection)
7467

7568
const result: Ref<TResult> = shallowRef(toValue(defaultValue))
7669
const meta = ref<CustomHookMeta>({})
7770

78-
const dataKey = ref(0)
79-
8071
// @TODO include nested relations in no-cache results
81-
const data = computed(() => {
82-
// eslint-disable-next-line ts/no-unused-expressions
83-
dataKey.value // track dataKey to force recompute
84-
72+
const cached = computed(() => {
8573
if (fetchPolicy !== 'no-cache') {
8674
const options = getOptions()
87-
const result = cacheMethod(options, meta.value) ?? null
88-
if (result && queryTrackingEnabled) {
89-
if (Array.isArray(result)) {
90-
return result.filter((item: WrappedItemBase<TCollection, TCollectionDefaults, TSchema>) => !item.$meta.dirtyQueries.has(trackingQueryId))
91-
}
92-
else {
93-
return !(result as unknown as WrappedItemBase<TCollection, TCollectionDefaults, TSchema>).$meta.dirtyQueries.has(trackingQueryId) ? result : null
94-
}
95-
}
96-
return result
75+
return cacheMethod(options, meta.value) ?? null
9776
}
9877
return result.value
9978
}) as Ref<TResult>
10079

80+
const queryTracking = queryTrackingEnabled
81+
? useQueryTracking<TResult>({
82+
store,
83+
result,
84+
cached,
85+
})
86+
: null
87+
10188
const loading = ref(false)
10289

10390
const error = ref<Error | null>(null)
10491

10592
const returnObject: VueQueryReturn<TCollection, TCollectionDefaults, TSchema, TResult> = {
106-
data,
93+
data: queryTracking?.filteredCached ?? cached,
10794
loading,
10895
error,
10996
refresh,
@@ -133,9 +120,7 @@ export function createQuery<
133120
...meta.value,
134121
$queryTracking: queryTrackingEnabled ? newQueryTracking : undefined,
135122
}).then(() => {
136-
if (queryTrackingEnabled) {
137-
handleQueryTracking(newQueryTracking)
138-
}
123+
queryTracking?.handleQueryTracking(newQueryTracking)
139124
})
140125
}
141126

@@ -151,7 +136,7 @@ export function createQuery<
151136
: meta.value)
152137

153138
if (queryTrackingEnabled && shouldHandleQueryTracking) {
154-
handleQueryTracking(newQueryTracking)
139+
queryTracking?.handleQueryTracking(newQueryTracking)
155140
}
156141
}
157142
catch (e: any) {
@@ -166,134 +151,6 @@ export function createQuery<
166151
return returnObject
167152
}
168153

169-
function handleQueryTracking(newQueryTracking: HookMetaQueryTracking) {
170-
const isResultEmpty = result.value == null || (Array.isArray(result.value) && result.value.length === 0)
171-
172-
// Init the query tracking object if the result is not empty and there is no previous tracking
173-
if (!isResultEmpty && !queryTracking) {
174-
const keys = Object.keys(newQueryTracking)
175-
const isNewQueryTrackingEmpty = keys.length === 0 || keys.every(k => newQueryTracking[k]!.size === 0)
176-
177-
if (isNewQueryTrackingEmpty) {
178-
const list = (Array.isArray(result.value) ? result.value : [result.value])
179-
for (const item of list) {
180-
if (item) {
181-
addToQueryTracking(newQueryTracking, item)
182-
}
183-
}
184-
}
185-
186-
{
187-
queryTracking = {}
188-
const list = Array.isArray(data.value) ? data.value : (data.value ? [data.value] : [])
189-
for (const item of list) {
190-
if (item) {
191-
addToQueryTracking(queryTracking, item)
192-
}
193-
}
194-
}
195-
196-
function addToQueryTracking(qt: HookMetaQueryTracking, item: WrappedItemBase<TCollection, TCollectionDefaults, TSchema>) {
197-
const collection = store.$collections.find(c => c.name === item.$collection)
198-
if (!collection) {
199-
throw new Error(`Collection ${item.$collection} not found in the store`)
200-
}
201-
const set = qt![collection.name] ??= new Set()
202-
if (set.has(item.$getKey())) {
203-
return
204-
}
205-
set.add(item.$getKey())
206-
for (const relationName in collection.relations) {
207-
const value = item[relationName as keyof typeof item]
208-
if (Array.isArray(value)) {
209-
for (const relatedItem of value) {
210-
if (relatedItem) {
211-
addToQueryTracking(qt, relatedItem as WrappedItemBase<TCollection, TCollectionDefaults, TSchema>)
212-
}
213-
}
214-
}
215-
}
216-
item.$meta.queries.add(trackingQueryId)
217-
}
218-
}
219-
220-
// Mark new tracked items as fresh
221-
for (const collectionName in newQueryTracking) {
222-
const collection = store.$collections.find(c => c.name === collectionName)!
223-
const oldKeys = queryTracking?.[collectionName]
224-
for (const key of newQueryTracking[collectionName]!) {
225-
const item = store.$cache.readItem({
226-
collection,
227-
key,
228-
}) as WrappedItemBase<TCollection, TCollectionDefaults, TSchema> | undefined
229-
if (item) {
230-
item.$meta.queries.add(trackingQueryId)
231-
item.$meta.dirtyQueries.delete(trackingQueryId)
232-
oldKeys?.delete(key)
233-
}
234-
}
235-
}
236-
237-
// Mark old tracked items as dirty if they are not tracked anymore
238-
let hasAddedDirty = false
239-
for (const collectionName in queryTracking) {
240-
const collection = store.$collections.find(c => c.name === collectionName)!
241-
for (const key of queryTracking[collectionName]!) {
242-
const item = store.$cache.readItem({
243-
collection,
244-
key,
245-
}) as WrappedItemBase<TCollection, TCollectionDefaults, TSchema> | undefined
246-
if (item) {
247-
item.$meta.queries.delete(trackingQueryId)
248-
item.$meta.dirtyQueries.add(trackingQueryId)
249-
250-
hasAddedDirty = true
251-
252-
// Clean garbage after the dirty items have a change to be removed from other queries
253-
// (e.g. after `dataKey.value++` updates the `data` computed property)
254-
nextTick(() => {
255-
store.$cache.garbageCollectItem({
256-
collection,
257-
item: item as any,
258-
})
259-
})
260-
}
261-
}
262-
}
263-
264-
// Filter out dirty items from the results
265-
if (hasAddedDirty) {
266-
// Force refresh of the data computed
267-
dataKey.value++
268-
}
269-
270-
queryTracking = newQueryTracking
271-
}
272-
273-
// Mark tracked items as dirty on unmount
274-
if (queryTrackingEnabled) {
275-
tryOnScopeDispose(() => {
276-
for (const collectionName in queryTracking) {
277-
const collection = store.$collections.find(c => c.name === collectionName)!
278-
for (const key of queryTracking[collectionName]!) {
279-
const item = store.$cache.readItem({
280-
collection,
281-
key,
282-
}) as WrappedItemBase<TCollection, TCollectionDefaults, TSchema> | undefined
283-
if (item) {
284-
item.$meta.queries.delete(trackingQueryId)
285-
item.$meta.dirtyQueries.add(trackingQueryId)
286-
287-
store.$cache.garbageCollectItem({
288-
collection,
289-
item: item as any,
290-
})
291-
}
292-
}
293-
}
294-
})
295-
}
296-
297154
// Auto load on options change
298155
watch(() => toValue(options), () => {
299156
load()

0 commit comments

Comments
 (0)