From 1ef9cef9fde233f32e22844f4786f2464c03051d Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 19 Nov 2025 15:58:58 +0100 Subject: [PATCH 1/5] Type tests reproducing the problem with the current type of the meta property --- .../query-db-collection/tests/query.test-d.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/packages/query-db-collection/tests/query.test-d.ts b/packages/query-db-collection/tests/query.test-d.ts index c8df9298e..b4f5140b0 100644 --- a/packages/query-db-collection/tests/query.test-d.ts +++ b/packages/query-db-collection/tests/query.test-d.ts @@ -5,13 +5,16 @@ import { createLiveQueryCollection, eq, gt, + parseLoadSubsetOptions, } from "@tanstack/db" import { QueryClient } from "@tanstack/query-core" import { z } from "zod" import { queryCollectionOptions } from "../src/query" +import type { QueryCollectionConfig } from "../src/query" import type { DeleteMutationFnParams, InsertMutationFnParams, + LoadSubsetOptions, UpdateMutationFnParams, } from "@tanstack/db" @@ -403,4 +406,69 @@ describe(`Query collection type resolution tests`, () => { expectTypeOf(selectUserData).parameters.toEqualTypeOf<[ResponseType]>() }) }) + + describe(`loadSubsetOptions type inference`, () => { + interface TestItem { + id: string + name: string + } + + it(`should type loadSubsetOptions as LoadSubsetOptions in queryFn`, () => { + const config: QueryCollectionConfig = { + id: `loadSubsetTest`, + queryClient, + queryKey: [`loadSubsetTest`], + queryFn: (ctx) => { + // Verify that loadSubsetOptions is assignable to LoadSubsetOptions + // This ensures it can be used where LoadSubsetOptions is expected + expectTypeOf( + ctx.meta!.loadSubsetOptions + ).toExtend() + // so that parseLoadSubsetOptions can be called without type errors + parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions) + // The fact that this call compiles without errors verifies that + // ctx.meta.loadSubsetOptions is typed correctly as LoadSubsetOptions + return Promise.resolve([]) + }, + getKey: (item) => item.id, + syncMode: `on-demand`, + } + + const options = queryCollectionOptions(config) + createCollection(options) + }) + + it(`should allow meta to contain additional properties beyond loadSubsetOptions`, () => { + const config: QueryCollectionConfig = { + id: `loadSubsetTest`, + queryClient, + queryKey: [`loadSubsetTest`], + queryFn: (ctx) => { + // Verify that an object with loadSubsetOptions plus other properties + // can be assigned to ctx.meta's type. This ensures the type is not too restrictive. + const metaWithExtra = { + loadSubsetOptions: ctx.meta!.loadSubsetOptions, + customProperty: `test`, + anotherProperty: 123, + } + + // Test that this object can be assigned to ctx.meta's type + // This verifies that ctx.meta allows additional properties beyond loadSubsetOptions + const typedMeta: typeof ctx.meta = metaWithExtra + + // Verify the assignment worked (this will fail at compile time if types don't match) + expectTypeOf( + typedMeta.loadSubsetOptions + ).toExtend() + + return Promise.resolve([]) + }, + getKey: (item) => item.id, + syncMode: `on-demand`, + } + + const options = queryCollectionOptions(config) + createCollection(options) + }) + }) }) From 0b866c6e83c25fe889f284b37c0eb85c25cdcd59 Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 19 Nov 2025 16:15:50 +0100 Subject: [PATCH 2/5] Augment tanstack query-core module to provide precise type for meta property --- packages/query-db-collection/src/index.ts | 1 + packages/query-db-collection/src/query.ts | 29 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packages/query-db-collection/src/index.ts b/packages/query-db-collection/src/index.ts index 1a3169b3f..989f15487 100644 --- a/packages/query-db-collection/src/index.ts +++ b/packages/query-db-collection/src/index.ts @@ -1,6 +1,7 @@ export { queryCollectionOptions, type QueryCollectionConfig, + type QueryCollectionMeta, type QueryCollectionUtils, type SyncOperation, } from "./query" diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index d5c469615..36c9e45d0 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -31,6 +31,35 @@ import type { StandardSchemaV1 } from "@standard-schema/spec" // Re-export for external use export type { SyncOperation } from "./manual-sync" +/** + * Base type for Query Collection meta properties. + * Users can extend this type when augmenting the @tanstack/query-core module + * to add their own custom properties while preserving loadSubsetOptions. + * + * @example + * ```typescript + * declare module "@tanstack/query-core" { + * interface Register { + * queryMeta: QueryCollectionMeta & { + * myCustomProperty: string + * } + * } + * } + * ``` + */ +export type QueryCollectionMeta = Record & { + loadSubsetOptions: LoadSubsetOptions +} + +// Module augmentation to extend TanStack Query's Register interface +// This ensures that ctx.meta always includes loadSubsetOptions +// We extend Record to preserve the ability to add other meta properties +declare module "@tanstack/query-core" { + interface Register { + queryMeta: QueryCollectionMeta + } +} + // Schema output type inference helper (matches electric.ts pattern) type InferSchemaOutput = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput extends object From dd2ba8bca1bb98cf4050c59fa12103fbf10d61ad Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 19 Nov 2025 16:32:01 +0100 Subject: [PATCH 3/5] Fix linting --- packages/query-db-collection/e2e/query.e2e.test.ts | 13 +++---------- packages/query-db-collection/tests/query.test.ts | 9 ++------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/query-db-collection/e2e/query.e2e.test.ts b/packages/query-db-collection/e2e/query.e2e.test.ts index c93b532c0..7e31efdc6 100644 --- a/packages/query-db-collection/e2e/query.e2e.test.ts +++ b/packages/query-db-collection/e2e/query.e2e.test.ts @@ -19,7 +19,6 @@ import { generateSeedData, } from "../../db-collection-e2e/src/index" import { applyPredicates, buildQueryKey } from "./query-filter" -import type { LoadSubsetOptions } from "@tanstack/db" import type { Comment as E2EComment, Post as E2EPost, @@ -94,9 +93,7 @@ describe(`Query Collection E2E Tests`, () => { queryKey: (opts) => buildQueryKey(`users`, opts), syncMode: `on-demand`, queryFn: (ctx) => { - const options = ctx.meta?.loadSubsetOptions as - | LoadSubsetOptions - | undefined + const options = ctx.meta?.loadSubsetOptions const filtered = applyPredicates(seedData.users, options) return Promise.resolve(filtered) }, @@ -112,9 +109,7 @@ describe(`Query Collection E2E Tests`, () => { queryKey: (opts) => buildQueryKey(`posts`, opts), syncMode: `on-demand`, queryFn: (ctx) => { - const options = ctx.meta?.loadSubsetOptions as - | LoadSubsetOptions - | undefined + const options = ctx.meta?.loadSubsetOptions const filtered = applyPredicates(seedData.posts, options) return Promise.resolve(filtered) }, @@ -130,9 +125,7 @@ describe(`Query Collection E2E Tests`, () => { queryKey: (opts) => buildQueryKey(`comments`, opts), syncMode: `on-demand`, queryFn: (ctx) => { - const options = ctx.meta?.loadSubsetOptions as - | LoadSubsetOptions - | undefined + const options = ctx.meta?.loadSubsetOptions const filtered = applyPredicates(seedData.comments, options) return Promise.resolve(filtered) }, diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index a1bdced77..f806d3bb6 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -13,7 +13,6 @@ import type { Collection, DeleteMutationFnParams, InsertMutationFnParams, - LoadSubsetOptions, TransactionWithMutations, UpdateMutationFnParams, } from "@tanstack/db" @@ -455,9 +454,7 @@ describe(`QueryCollection`, () => { const queryFn = vi .fn() .mockImplementation((ctx: QueryFunctionContext) => { - const loadSubsetOptions = ctx.meta?.loadSubsetOptions as - | LoadSubsetOptions - | undefined + const loadSubsetOptions = ctx.meta?.loadSubsetOptions // Verify where clause is present expect(loadSubsetOptions?.where).toBeDefined() expect(loadSubsetOptions?.where).not.toBeNull() @@ -515,9 +512,7 @@ describe(`QueryCollection`, () => { const queryFn = vi .fn() .mockImplementation((ctx: QueryFunctionContext) => { - const loadSubsetOptions = ctx.meta?.loadSubsetOptions as - | LoadSubsetOptions - | undefined + const loadSubsetOptions = ctx.meta?.loadSubsetOptions // Verify where clause is present (this was the bug - it was undefined/null before the fix) expect(loadSubsetOptions?.where).toBeDefined() expect(loadSubsetOptions?.where).not.toBeNull() From a81a4c58b1c7073dfaff6fee08ea092199ea2f48 Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Thu, 20 Nov 2025 11:03:27 +0100 Subject: [PATCH 4/5] Changeset --- .changeset/eighty-ideas-clean.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eighty-ideas-clean.md diff --git a/.changeset/eighty-ideas-clean.md b/.changeset/eighty-ideas-clean.md new file mode 100644 index 000000000..c0eb9abe5 --- /dev/null +++ b/.changeset/eighty-ideas-clean.md @@ -0,0 +1,5 @@ +--- +"@tanstack/query-db-collection": patch +--- + +Improved the type of the queryFn's ctx.meta property of the Query Collection to include the loadSubsetOptions From af196fa3e5c2d394b219a92ba4180b8f39bd750a Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Thu, 20 Nov 2025 12:13:23 +0100 Subject: [PATCH 5/5] Fix failing unit test after rebase --- packages/query-db-collection/tests/query.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index f806d3bb6..e0807232e 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -3673,9 +3673,7 @@ describe(`QueryCollection`, () => { ] const queryFn = vi.fn((ctx: QueryFunctionContext) => { - const loadSubsetOptions = ctx.meta?.loadSubsetOptions as - | LoadSubsetOptions - | undefined + const loadSubsetOptions = ctx.meta?.loadSubsetOptions // Filter items based on the where clause if present if (loadSubsetOptions?.where) { // Simple mock filtering - in real use, you'd use parseLoadSubsetOptions