Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eighty-ideas-clean.md
Original file line number Diff line number Diff line change
@@ -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
13 changes: 3 additions & 10 deletions packages/query-db-collection/e2e/query.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
},
Expand All @@ -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)
},
Expand All @@ -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)
},
Expand Down
1 change: 1 addition & 0 deletions packages/query-db-collection/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export {
queryCollectionOptions,
type QueryCollectionConfig,
type QueryCollectionMeta,
type QueryCollectionUtils,
type SyncOperation,
} from "./query"
Expand Down
29 changes: 29 additions & 0 deletions packages/query-db-collection/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown> & {
loadSubsetOptions: LoadSubsetOptions
}

// Module augmentation to extend TanStack Query's Register interface
// This ensures that ctx.meta always includes loadSubsetOptions
// We extend Record<string, unknown> 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> = T extends StandardSchemaV1
? StandardSchemaV1.InferOutput<T> extends object
Expand Down
68 changes: 68 additions & 0 deletions packages/query-db-collection/tests/query.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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<TestItem> = {
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<LoadSubsetOptions>()
// 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<TestItem> = {
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<LoadSubsetOptions>()

return Promise.resolve([])
},
getKey: (item) => item.id,
syncMode: `on-demand`,
}

const options = queryCollectionOptions(config)
createCollection(options)
})
})
})
13 changes: 3 additions & 10 deletions packages/query-db-collection/tests/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
Collection,
DeleteMutationFnParams,
InsertMutationFnParams,
LoadSubsetOptions,
TransactionWithMutations,
UpdateMutationFnParams,
} from "@tanstack/db"
Expand Down Expand Up @@ -455,9 +454,7 @@ describe(`QueryCollection`, () => {
const queryFn = vi
.fn()
.mockImplementation((ctx: QueryFunctionContext<any>) => {
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()
Expand Down Expand Up @@ -515,9 +512,7 @@ describe(`QueryCollection`, () => {
const queryFn = vi
.fn()
.mockImplementation((ctx: QueryFunctionContext<any>) => {
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()
Expand Down Expand Up @@ -3678,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
Expand Down
Loading