Problem
When calling a query function with an object that has extra properties beyond what the schema defines, the cache key includes those extra properties. This causes cache misses between .set() calls (which use only schema-relevant fields) and reads from pages that pass a superset object (like route params).
Reproduction
// versions.remote.ts
const getVersionSchema = z.object({
orgAppId: z.string(),
versionId: z.string(),
});
export const getVersion = query(getVersionSchema, async ({ orgAppId, versionId }) => {
return await db.version.findUnique({ where: { id: versionId } });
});
<!-- /org/[organizationId]/versions/[versionId]/+page.svelte -->
<script lang="ts">
// params = { organizationId, orgAppId, versionId }
const version = $derived(await resolve(getVersion(params)));
</script>
// After mutation:
void getVersion({ orgAppId: data.orgAppId, versionId: data.id }).set(updated);
The .set() produces a cache key from { orgAppId, versionId }, but the page computes a key from { organizationId, orgAppId, versionId }. These keys differ, so the page shows stale data until a full reload.
Expected behavior
The cache key should only consider properties defined in the schema, since extra properties are stripped by Zod on the server anyway. Passing { orgAppId, versionId, organizationId } and { orgAppId, versionId } to the same query should produce the same cache key.
Suggested solution
On the client side, before computing the cache key, strip the input object to only include keys that the server schema defines. Some options:
-
Ship schema key names to the client — during compilation, extract the top-level keys from the Zod schema and embed them in the query metadata. The client uses this key list to filter the input before serialization.
-
Use schema.parse() on the server and return the normalized key — the server already validates the input; include the canonical cache key in the response so the client can reconcile.
-
Document the footgun — at minimum, warn in the docs that passing extra properties produces a different cache key than .set() with only schema fields.
Workaround
Explicitly destructure only the needed fields at each call site:
const version = $derived(
await resolve(getVersion({ orgAppId: params.orgAppId, versionId: params.versionId }))
);
This is error-prone since TypeScript's structural typing doesn't flag extra properties on existing variables.
Problem
When calling a
queryfunction with an object that has extra properties beyond what the schema defines, the cache key includes those extra properties. This causes cache misses between.set()calls (which use only schema-relevant fields) and reads from pages that pass a superset object (like routeparams).Reproduction
The
.set()produces a cache key from{ orgAppId, versionId }, but the page computes a key from{ organizationId, orgAppId, versionId }. These keys differ, so the page shows stale data until a full reload.Expected behavior
The cache key should only consider properties defined in the schema, since extra properties are stripped by Zod on the server anyway. Passing
{ orgAppId, versionId, organizationId }and{ orgAppId, versionId }to the same query should produce the same cache key.Suggested solution
On the client side, before computing the cache key, strip the input object to only include keys that the server schema defines. Some options:
Ship schema key names to the client — during compilation, extract the top-level keys from the Zod schema and embed them in the query metadata. The client uses this key list to filter the input before serialization.
Use
schema.parse()on the server and return the normalized key — the server already validates the input; include the canonical cache key in the response so the client can reconcile.Document the footgun — at minimum, warn in the docs that passing extra properties produces a different cache key than
.set()with only schema fields.Workaround
Explicitly destructure only the needed fields at each call site:
const version = $derived( await resolve(getVersion({ orgAppId: params.orgAppId, versionId: params.versionId })) );This is error-prone since TypeScript's structural typing doesn't flag extra properties on existing variables.