Skip to content

Commit 98cbe36

Browse files
committed
fix(queryCache): track & dispose keys on instance cleanup
1 parent 82408d2 commit 98cbe36

File tree

5 files changed

+58
-21
lines changed

5 files changed

+58
-21
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default defineNuxtRouteMiddleware((to) => {
2+
console.log("CLEANUP");
3+
});

playground/pages/examples/basic-text.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const searchQuery = useRouteQuery({
1111
const username = useRouteQuery({
1212
key: 'user',
1313
schema: z.string(),
14-
default: ''
14+
default: '',
1515
})
1616
1717
const resetToDefaults = () => {

playground/pages/examples/nested.vue

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@
3737

3838
<div class="space-y-4">
3939
<UFormField label="Theme">
40-
<USelectMenu v-model="userProfile.preferences.theme" :options="themeOptions"
41-
value-attribute="value" option-attribute="label" />
40+
<USelectMenu v-model="userProfile.preferences.theme" :items="themeOptions"
41+
value-key="value" />
4242
</UFormField>
4343

4444
<UFormField label="Language">
45-
<USelectMenu v-model="userProfile.preferences.language" :options="languageOptions"
46-
value-attribute="value" option-attribute="label" />
45+
<USelectMenu v-model="userProfile.preferences.language" :items="languageOptions"
46+
value-key="value" />
4747
</UFormField>
4848

4949
<UFormField label="Notifications">
@@ -114,16 +114,16 @@ const userProfile = useRouteQuery({
114114
email: z.string(),
115115
phone: z.string()
116116
}),
117-
preferences: z.record(z.string(), z.any())
118-
// z.object({
119-
// theme: z.enum(['light', 'dark', 'system']),
120-
// language: z.string(),
121-
// notifications: z.object({
122-
// email: z.boolean(),
123-
// push: z.boolean(),
124-
// sms: z.boolean()
125-
// })
126-
// })
117+
preferences:
118+
z.object({
119+
theme: z.enum(['light', 'dark', 'system']),
120+
language: z.string(),
121+
notifications: z.object({
122+
email: z.boolean(),
123+
push: z.boolean(),
124+
sms: z.boolean()
125+
})
126+
})
127127
},
128128
default: {
129129
personal: {

src/queryManager.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import type { GenericObject } from "./types";
77
*/
88
export class GlobalQueryManager {
99
private static instance: GlobalQueryManager;
10-
private updates: Map<string, { value: any; mode: "push" | "replace" }> =
11-
new Map();
10+
private updates: Map<string, { value: any; mode: "push" | "replace" }> = new Map();
1211
private processingPromise: Promise<void> | null = null;
1312
private router: any = null;
1413
private currentQuery: Record<string, any> = {};
1514
private initialQueryHandled = false;
15+
private instances = new Map<symbol, string[]>();
1616

1717
private constructor() {}
1818

@@ -31,6 +31,27 @@ export class GlobalQueryManager {
3131
}
3232
}
3333

34+
registerInstance(instanceId: symbol, schemaKeys: string[]) {
35+
this.instances.set(instanceId, schemaKeys);
36+
}
37+
38+
unregisterInstance(instanceId: symbol) {
39+
const keys = this.instances.get(instanceId);
40+
if(!keys) return;
41+
42+
this.instances.delete(instanceId);
43+
for (const key of keys) {
44+
if(!this.isKeyOwnedByInstance(key)) delete this.currentQuery[key];
45+
}
46+
}
47+
48+
isKeyOwnedByInstance(key: string) {
49+
for (const [instanceId, keys] of this.instances.entries())
50+
if (keys.includes(key)) return true;
51+
return false;
52+
}
53+
54+
3455
enqueue(key: string, value: any, mode: "push" | "replace" = "replace") {
3556
this.updates.set(key, { value, mode });
3657

@@ -126,4 +147,9 @@ export class GlobalQueryManager {
126147
this.enqueue(key, undefined, mode);
127148
});
128149
}
150+
151+
static cleanup() {
152+
// @ts-expect-error
153+
GlobalQueryManager.instance = null;
154+
}
129155
}

src/useRouteQuery.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// useRouteQuery.ts
2-
import { type Ref, ref, toRaw, watch } from "vue";
2+
import { type Ref, onUnmounted, ref, toRaw, watch } from "vue";
33
import { useRoute, useRouter } from "vue-router";
44
import { z } from "zod";
55
import { GlobalQueryManager } from "./queryManager";
@@ -30,7 +30,9 @@ export function useRouteQuery<
3030
const route = useRoute();
3131
const router = useRouter();
3232
const defaultValue = params.default;
33-
const mode = params.mode ?? "replace"; // Default to 'replace' if not specified
33+
const mode = params.mode ?? "replace";
34+
const instanceId = Symbol();
35+
// Default to 'replace' if not specified
3436

3537
if (params.debug)
3638
console.log("useRouteQuery init with:", {
@@ -40,14 +42,20 @@ export function useRouteQuery<
4042
mode: mode,
4143
});
4244
const queryManager = GlobalQueryManager.getInstance();
43-
const { nullable = false, key: rootKey } = params;
4445

45-
// Validate that single value schemas have a key
46+
const { nullable = false, key: rootKey } = params;
4647
if (params.schema instanceof z.ZodType && !rootKey) {
4748
throw new Error("key is required for single value schemas");
4849
}
4950

51+
const instanceKeys = params.schema instanceof z.ZodType || params.key
52+
? [params.key!]
53+
: Object.keys(params.schema)
54+
5055
queryManager.init(router, route.query);
56+
queryManager.registerInstance(instanceId, instanceKeys);
57+
58+
onUnmounted(() => queryManager.unregisterInstance(instanceId));
5159

5260
const baseSchema =
5361
params.schema instanceof z.ZodType

0 commit comments

Comments
 (0)