T
TanStackβ€’2w ago
flat-fuchsia

queryCollectionOptions type error missing compareOptions/comparisonOpts

Hi, I'm pretty new working with DB, I've been working with it for about a month now all perfect and today after upgrading to the latests version of DB, getting type errors when using queryCollectionOptions with createCollection: Type 'Collection<T, string, any, any, any>' is missing properties: comparisonOpts, compareOptions These properties were added in PR #762 Currently using as any workaround but losing all type inference. Regarding code, it doesn't matter if I add a full working collection or a minimum example, the error shows up. The code in the first screenshot is the todos example in the JSDocs of the queryCollectionOptions for testing. I'm adding a third image with the workaround I'm using, as any to avoid the error showing up and the type inference to get type safety again since without it I'm getting something like: name: {} | undefined I'm not sure if this is a "me" issue or something else. (Thank you for this amazing library and sorry if I'm not reporting this correctly, literally my first message in the discord)
No description
No description
No description
32 Replies
harsh-harlequin
harsh-harlequinβ€’2w ago
ah thanks for the through bug report! https://github.com/TanStack/db/pull/816
GitHub
Fix queryCollectionOptions type error after upgrade by KyleAMathews...
This fixes a TypeScript error where queryCollectionOptions (and other collection options functions) produce Collections that TypeScript reports as missing the compareOptions property. Reported on d...
rare-sapphire
rare-sapphireβ€’2w ago
I tried reproducing this type error but i couldn't:
const todoSchema = z.object({
id: z.string(),
title: z.string(),
completed: z.boolean(),
})

type Todo = z.infer<typeof todoSchema>

const queryClient = new QueryClient()

const todosCollection = createCollection(
queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => {
const response = await fetch("/api/todos")
return response.json() as Promise<Todo[]>
},
queryClient,
getKey: (item) => item.id,
})
)
const todoSchema = z.object({
id: z.string(),
title: z.string(),
completed: z.boolean(),
})

type Todo = z.infer<typeof todoSchema>

const queryClient = new QueryClient()

const todosCollection = createCollection(
queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => {
const response = await fetch("/api/todos")
return response.json() as Promise<Todo[]>
},
queryClient,
getKey: (item) => item.id,
})
)
This type checks without error. Note that i has to change your typecast to return response.json as Promise<Todo[]> because .json() returns a promise. @Ozmah i also posted my comment on the GH issue: https://github.com/TanStack/db/pull/816 Please reply on the GH issue as that provides better visibility for everyone interested in the bug
harsh-harlequin
harsh-harlequinβ€’2w ago
ok so close the PR?
rare-sapphire
rare-sapphireβ€’2w ago
I would close the PR but you added the fix for that other bug in it. So i would move your fix to a separate PR and then close this one (we can always reopen it if the OP comes back with an example we can reproduce)
harsh-harlequin
harsh-harlequinβ€’2w ago
oh changing it to public readonly compareOptions?
rare-sapphire
rare-sapphireβ€’2w ago
I was proposing that, yes. But i don't see how that or claude's changes are solving the problem. Mainly, because i don't see what the problem is. I tried reproducing the type error reported here and i could not reproduce it. So i don't think there is any problem, i.e. no code changes needed?
harsh-harlequin
harsh-harlequinβ€’2w ago
cool, I'll just change the PR name/body
rare-sapphire
rare-sapphireβ€’2w ago
Ok, but pls also remove any changes that are unrelated to this. i.e. claude's first few commits.
harsh-harlequin
harsh-harlequinβ€’2w ago
hmm? There's just the change from the getter to public readonly now β€” https://github.com/TanStack/db/pull/816/files
rare-sapphire
rare-sapphireβ€’2w ago
What problem does this solve?
harsh-harlequin
harsh-harlequinβ€’2w ago
nothing apparently β€” researched it again and ts treats them the same 🀷 closing
flat-fuchsia
flat-fuchsiaOPβ€’2w ago
I've been following the PR and will post back with new reproducible code as soon as I get out of work if it helps still in any way πŸ™‚
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
Since the PR was closed I'm replying here. I took the code example you created and simply pasted it in two different code bases I have using DB, both times the error shows up, it doesn't create critical errors of any kind but removes the typesafety entirely. Added two screenshots, one showing the error and one showing the code example provided previously. This is the complete stack: - Bun - Start - DB - Form - ElysiaJS Versions:
"@tanstack/query-db-collection": "^0.2.42",
"@tanstack/react-db": "^0.1.46",
"@tanstack/react-form": "^1.25.0",
"@tanstack/react-query": "^5.90.10",
"@tanstack/react-router": "^1.136.13",
"@tanstack/react-router-with-query": "^1.130.17",
"@tanstack/react-start": "^1.136.13",
"@tanstack/router-plugin": "^1.136.13",
"vite": "^7.2.2",
"elysia": "^1.4.16",
"@tanstack/query-db-collection": "^0.2.42",
"@tanstack/react-db": "^0.1.46",
"@tanstack/react-form": "^1.25.0",
"@tanstack/react-query": "^5.90.10",
"@tanstack/react-router": "^1.136.13",
"@tanstack/react-router-with-query": "^1.130.17",
"@tanstack/react-start": "^1.136.13",
"@tanstack/router-plugin": "^1.136.13",
"vite": "^7.2.2",
"elysia": "^1.4.16",
I'm also going to try this using the tanstack starter, installing DB from scratch and try it using npm instead of Bun. In the meantime, is there anything in particular you'd like me to share about the codebases I've tried it in or anything in particular I can help with? I'm on MΓ©xico's timezone and life got a bit in the way, that's why it took me so long to respond, I apologize for that.
No description
No description
harsh-harlequin
harsh-harlequinβ€’7d ago
What's the type errors?
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
I'm sorry, what do you mean by the type errors?
harsh-harlequin
harsh-harlequinβ€’7d ago
like all the text the ts compiler spits out when you hover over an error β€” they try to tell you what the error is β€” we can't read the full thing from the first screenshot
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
oh, of course give me a second and I'll paste the whole thing
No overload matches this call.
The last overload gave the following error.
Argument of type 'CollectionConfig<Record<string, unknown>, string | number, StandardSchemaV1<unknown, unknown>, UtilsRecord> & { schema: StandardSchemaV1<unknown, unknown>; utils: QueryCollectionUtils<...>; }' is not assignable to parameter of type 'CollectionConfig<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, never, UtilsRecord & QueryCollectionUtils<Record<string, unknown>, string | number, Record<...>, unknown>> & { ...; } & SingleResult'.
Type 'CollectionConfig<Record<string, unknown>, string | number, StandardSchemaV1<unknown, unknown>, UtilsRecord> & { schema: StandardSchemaV1<unknown, unknown>; utils: QueryCollectionUtils<...>; }' is not assignable to type 'CollectionConfig<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, never, UtilsRecord & QueryCollectionUtils<Record<string, unknown>, string | number, Record<...>, unknown>>'.
The types of 'sync.sync' are incompatible between these types.
Type '(params: { collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes' is not assignable to type '(params: { collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes'.
Types of parameters 'params' and 'params' are incompatible.
Type '{ collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }' is not assignable to type '{ collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }'.
The types of 'collection.config.sync.sync' are incompatible between these types.
Type '(params: { collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes' is not assignable to type '(params: { collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes'.
Types of parameters 'params' and 'params' are incompatible.
Type '{ collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }' is not assignable to type '{ collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }'.
Types of property 'collection' are incompatible.
Type 'Collection<Record<string, unknown>, string | number, any, any, any>' is missing the following properties from type 'Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>': comparisonOpts, compareOptions (ts 2769)
No overload matches this call.
The last overload gave the following error.
Argument of type 'CollectionConfig<Record<string, unknown>, string | number, StandardSchemaV1<unknown, unknown>, UtilsRecord> & { schema: StandardSchemaV1<unknown, unknown>; utils: QueryCollectionUtils<...>; }' is not assignable to parameter of type 'CollectionConfig<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, never, UtilsRecord & QueryCollectionUtils<Record<string, unknown>, string | number, Record<...>, unknown>> & { ...; } & SingleResult'.
Type 'CollectionConfig<Record<string, unknown>, string | number, StandardSchemaV1<unknown, unknown>, UtilsRecord> & { schema: StandardSchemaV1<unknown, unknown>; utils: QueryCollectionUtils<...>; }' is not assignable to type 'CollectionConfig<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, never, UtilsRecord & QueryCollectionUtils<Record<string, unknown>, string | number, Record<...>, unknown>>'.
The types of 'sync.sync' are incompatible between these types.
Type '(params: { collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes' is not assignable to type '(params: { collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes'.
Types of parameters 'params' and 'params' are incompatible.
Type '{ collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }' is not assignable to type '{ collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }'.
The types of 'collection.config.sync.sync' are incompatible between these types.
Type '(params: { collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes' is not assignable to type '(params: { collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }) => void | ... 1 more ... | SyncConfigRes'.
Types of parameters 'params' and 'params' are incompatible.
Type '{ collection: Collection<Record<string, unknown>, string | number, any, any, any>; begin: () => void; write: (message: Omit<ChangeMessage<Record<string, unknown>, string | number>, "key">) => void; commit: () => void; markReady: () => void; truncate: () => void; }' is not assignable to type '{ collection: Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>; ... 4 more ...; truncate: () => void; }'.
Types of property 'collection' are incompatible.
Type 'Collection<Record<string, unknown>, string | number, any, any, any>' is missing the following properties from type 'Collection<Record<string, unknown> | (Partial<Record<string, unknown>> & Record<string, unknown>), string | number, any, any, any>': comparisonOpts, compareOptions (ts 2769)
harsh-harlequin
harsh-harlequinβ€’7d ago
one problem here is you don't pass in your schema β€” but it should error without a schema
harsh-harlequin
harsh-harlequinβ€’7d ago
looks like this is the fix https://github.com/TanStack/db/pull/859 cc @kevindp
GitHub
Fix TypeScript type resolution for QueryCollectionConfig when using...
Previously, the QueryCollectionConfig interface extended BaseCollectionConfig, but TypeScript failed to resolve inherited properties like getKey, onInsert, onUpdate, etc. when the interface contain...
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
Please let me know if there's anything else I can do to help :PeepoHappy:
harsh-harlequin
harsh-harlequinβ€’7d ago
Did you try adding your schema?
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
I'll move around some code to get a better example with a schema to see the results and get back to you.
harsh-harlequin
harsh-harlequinβ€’7d ago
I mean your screenshot above has todoSchema. You can just pass it in as your schema
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
Oh sure what I meant is that I'm not at the computer now, I'll go back to it later tonight and I'll do that and maybe one more example. I'm sending two different examples, one using the todo example by @kevindp and one taken from one app I'm working on at the moment (pardon my code πŸ˜… ). Note, I made a fresh installation for both the todo example and for my app to avoid any version collisions on either environment. The first screenshot is the same todo example including the original schema provided, the error is the same, if I add the as any fix I mentioned in the original post, we can check the schema is being used (screenshot 2). For the second example, this is a collection I'm already using:
import { CreateIngredientCategorySchemaTSDB } from "@la-comida/shared/schemas/ingredient-category";
import { queryCollectionOptions } from "@tanstack/query-db-collection";
import {
createCollection,
} from "@tanstack/react-db";
import { app as elysia } from "@web/lib/api";
import { queryClient } from "@web/lib/query-client";

export const ingredientCategoriesCollection = createCollection(
queryCollectionOptions({
queryKey: ["ingredient-categories"],
queryFn: async () => {
const { data, error } = await elysia.api["ingredient-categories"].get();

if (error) {
throw new Error(`Failed to fetch categories: ${error}`);
}

return data ?? [];
},
queryClient,
getKey: (item) => item.id,

onInsert: async ({ transaction }) => {
const items = transaction.mutations[0];
const { error } = await elysia.api["ingredient-categories"].post(
items.modified,
);
if (error) {
if (error.status === 422) {
const validationError = error.value as unknown as {
error: string;
details: string;
};
throw new Error(validationError.details || validationError.error);
}

const serverError = error.value as unknown as { error: string };
throw new Error(serverError.error);
}
},

onUpdate: async ({ transaction }) => {
const mutation = transaction.mutations[0];
const { error } = await elysia.api["ingredient-categories"]({ id: mutation.original.id })
.put(mutation.modified);
if (error) throw new Error(`Update failed: ${error}`);
},

onDelete: async ({ transaction }) => {
const mutation = transaction.mutations[0];
const { error } = await elysia.api["ingredient-categories"]({ id: mutation.original.id }).delete()

if (error) throw new Error(`Delete failed: ${error}`);
},

schema: CreateIngredientCategorySchemaTSDB,
})
);
import { CreateIngredientCategorySchemaTSDB } from "@la-comida/shared/schemas/ingredient-category";
import { queryCollectionOptions } from "@tanstack/query-db-collection";
import {
createCollection,
} from "@tanstack/react-db";
import { app as elysia } from "@web/lib/api";
import { queryClient } from "@web/lib/query-client";

export const ingredientCategoriesCollection = createCollection(
queryCollectionOptions({
queryKey: ["ingredient-categories"],
queryFn: async () => {
const { data, error } = await elysia.api["ingredient-categories"].get();

if (error) {
throw new Error(`Failed to fetch categories: ${error}`);
}

return data ?? [];
},
queryClient,
getKey: (item) => item.id,

onInsert: async ({ transaction }) => {
const items = transaction.mutations[0];
const { error } = await elysia.api["ingredient-categories"].post(
items.modified,
);
if (error) {
if (error.status === 422) {
const validationError = error.value as unknown as {
error: string;
details: string;
};
throw new Error(validationError.details || validationError.error);
}

const serverError = error.value as unknown as { error: string };
throw new Error(serverError.error);
}
},

onUpdate: async ({ transaction }) => {
const mutation = transaction.mutations[0];
const { error } = await elysia.api["ingredient-categories"]({ id: mutation.original.id })
.put(mutation.modified);
if (error) throw new Error(`Update failed: ${error}`);
},

onDelete: async ({ transaction }) => {
const mutation = transaction.mutations[0];
const { error } = await elysia.api["ingredient-categories"]({ id: mutation.original.id }).delete()

if (error) throw new Error(`Delete failed: ${error}`);
},

schema: CreateIngredientCategorySchemaTSDB,
})
);
And this is the schema:
export const IngredientCategorySchema = z.object({
id: z.uuid(),
userId: z.string().min(1).max(36),
name: z.string().min(1).max(100),
color: z
.string()
.regex(/^#[0-9A-F]{6}$/i)
.length(7),
icon: z.string().min(1).max(50),
isDefault: z.boolean().default(false),
createdAt: z.coerce.date(),
});


export const CreateIngredientCategorySchemaTSDB = IngredientCategorySchema.omit(
{
userId: true,
createdAt: true,
isDefault: true,
},
);
export const IngredientCategorySchema = z.object({
id: z.uuid(),
userId: z.string().min(1).max(36),
name: z.string().min(1).max(100),
color: z
.string()
.regex(/^#[0-9A-F]{6}$/i)
.length(7),
icon: z.string().min(1).max(50),
isDefault: z.boolean().default(false),
createdAt: z.coerce.date(),
});


export const CreateIngredientCategorySchemaTSDB = IngredientCategorySchema.omit(
{
userId: true,
createdAt: true,
isDefault: true,
},
);
In all my collections I've used a schema every time. If you need me to post any of this information in the PR or any other information, please let me know, I'll be happy to help :PeepoHappy:
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
No description
No description
rare-sapphire
rare-sapphireβ€’7d ago
@Ozmah i think your IDE might be confused. The todo code snippet compiles without (type) errors on my machine. Sometimes IntelliSense in VS Code gets confused, use the TypeScript compiler as the source of truth, if you compile it does TypeScript complain?
flat-fuchsia
flat-fuchsiaOPβ€’7d ago
I'll try it tomorrow morning but just in case it's relevant, this happens in vscode and zed, all screenshots I've shared are from zed mostly. I'll give all this a go using the tanstack starter and db fresh install as well, might be related to my stack πŸ€”
rare-sapphire
rare-sapphireβ€’7d ago
Yep, couldn't reproduce on my side. I'm testing it on latest version of our main branch.
flat-fuchsia
flat-fuchsiaOPβ€’6d ago
Hi, I've been running several tests and it seems @kevindp was right all along, this is a "me" issue, not sure how yet but it's bun related. Sorry guys for wasting your time, I'll make sure to investigate this and I'll be a lot more thorough testing the library in the future to avoid doing something like this again.
rare-sapphire
rare-sapphireβ€’6d ago
No worries. It happens to all of us!
flat-fuchsia
flat-fuchsiaβ€’5d ago
@Ozmah did you ever figure out what was causing that error in the editor on your end. I’m seeing it also. It compiles and seems to work correctly but I would love to fix whatever is causing it. I’m seeing the error in cursor. Moved
flat-fuchsia
flat-fuchsiaOPβ€’4d ago
Hi Dan, sorry I didn't see the message till now. So far I haven't had time to investigate in depth but I do know bun is thinking I have two versions of DB installed despite cleaning everything related to package.json or even a new installation. I'll have time to investigate next week and if I find anything noteworthy I'll get back to you :PeepoHappy:

Did you find this page helpful?