From 7cb3d6626171fa2e54468dc18a9566dde74accd2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Nov 2025 17:56:57 +0000 Subject: [PATCH 1/7] fix: ensure module augmentation for ctx.meta is automatically loaded Previously, the module augmentation for @tanstack/query-core that makes ctx.meta?.loadSubsetOptions type-safe was in query.ts. This meant it wasn't always processed by TypeScript unless something from that file was directly imported. This commit fixes the issue by: 1. Moving the module augmentation to a dedicated global.d.ts file 2. Adding a triple-slash reference in index.ts to ensure global.d.ts is always loaded when the package is imported 3. Removing the duplicate module augmentation from query.ts Now, ctx.meta?.loadSubsetOptions is automatically typed as LoadSubsetOptions without requiring users to explicitly import QueryCollectionMeta or add any manual type assertions. Fixes the issue where users needed to use @ts-ignore or manual type assertions to pass ctx.meta?.loadSubsetOptions to parseLoadSubsetOptions. --- packages/query-db-collection/src/global.d.ts | 40 +++++++++++++++++++ packages/query-db-collection/src/index.ts | 6 ++- packages/query-db-collection/src/query.ts | 29 -------------- .../query-db-collection/tests/query.test-d.ts | 35 ++++++++++++++++ 4 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 packages/query-db-collection/src/global.d.ts diff --git a/packages/query-db-collection/src/global.d.ts b/packages/query-db-collection/src/global.d.ts new file mode 100644 index 000000000..25ef58a0d --- /dev/null +++ b/packages/query-db-collection/src/global.d.ts @@ -0,0 +1,40 @@ +/** + * Global type augmentation for @tanstack/query-core + * + * This file ensures the module augmentation is always loaded when the package is imported. + * The index.ts file includes a triple-slash reference to this file, which guarantees + * TypeScript processes it whenever anyone imports from @tanstack/query-db-collection. + * + * This makes ctx.meta?.loadSubsetOptions automatically type-safe without requiring + * users to manually import QueryCollectionMeta. + */ + +import type { LoadSubsetOptions } from "@tanstack/db" + +/** + * 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: import("@tanstack/query-db-collection").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 +declare module "@tanstack/query-core" { + interface Register { + queryMeta: QueryCollectionMeta + } +} diff --git a/packages/query-db-collection/src/index.ts b/packages/query-db-collection/src/index.ts index 989f15487..9f23731e4 100644 --- a/packages/query-db-collection/src/index.ts +++ b/packages/query-db-collection/src/index.ts @@ -1,11 +1,15 @@ +/// + export { queryCollectionOptions, type QueryCollectionConfig, - type QueryCollectionMeta, type QueryCollectionUtils, type SyncOperation, } from "./query" +// Export QueryCollectionMeta from global.d.ts +export type { QueryCollectionMeta } from "./global" + export * from "./errors" // Re-export expression helpers from @tanstack/db diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 36c9e45d0..d5c469615 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -31,35 +31,6 @@ 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 diff --git a/packages/query-db-collection/tests/query.test-d.ts b/packages/query-db-collection/tests/query.test-d.ts index b4f5140b0..c5970847b 100644 --- a/packages/query-db-collection/tests/query.test-d.ts +++ b/packages/query-db-collection/tests/query.test-d.ts @@ -470,5 +470,40 @@ describe(`Query collection type resolution tests`, () => { const options = queryCollectionOptions(config) createCollection(options) }) + + it(`should have loadSubsetOptions typed automatically without explicit QueryCollectionMeta import`, () => { + // This test validates that the module augmentation works automatically + // Note: We are NOT importing QueryCollectionMeta, yet ctx.meta.loadSubsetOptions + // should still be properly typed as LoadSubsetOptions + const config: QueryCollectionConfig = { + id: `autoTypeTest`, + queryClient, + queryKey: [`autoTypeTest`], + queryFn: (ctx) => { + // This should compile without errors because the module augmentation + // in global.d.ts is automatically loaded via the triple-slash reference + // in index.ts + const options = ctx.meta?.loadSubsetOptions + + // Verify the type is correct + expectTypeOf(options).toMatchTypeOf() + + // Verify it can be passed to parseLoadSubsetOptions without type errors + const parsed = parseLoadSubsetOptions(options) + expectTypeOf(parsed).toMatchTypeOf<{ + filters: Array + sorts: Array + limit?: number + }>() + + return Promise.resolve([]) + }, + getKey: (item) => item.id, + syncMode: `on-demand`, + } + + const options = queryCollectionOptions(config) + createCollection(options) + }) }) }) From 9000585ce27f26639c7ab7e8a5c599928fec14a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Nov 2025 19:10:39 +0000 Subject: [PATCH 2/7] fix: change QueryCollectionMeta to interface for proper extensibility This commit addresses two critical issues identified in code review: 1. **Fixed double export**: Removed duplicate QueryCollectionMeta export from the query.ts line in index.ts. QueryCollectionMeta is now only exported from global.d.ts, which is its canonical source. 2. **Changed from type alias to interface**: Converting QueryCollectionMeta from a type alias to an interface enables proper TypeScript declaration merging. This allows users to extend meta with custom properties without encountering "Subsequent property declarations must have the same type" errors. The updated documentation now shows the correct pattern for users to extend meta: ```typescript declare module "@tanstack/query-db-collection" { interface QueryCollectionMeta { myCustomProperty: string } } ``` This is safer than the previous pattern which would have caused users to collide with the library's own Register.queryMeta augmentation. Added a type test documenting the extension pattern for future reference. --- packages/query-db-collection/src/global.d.ts | 17 ++++---- packages/query-db-collection/src/index.ts | 2 +- .../query-db-collection/tests/query.test-d.ts | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/packages/query-db-collection/src/global.d.ts b/packages/query-db-collection/src/global.d.ts index 25ef58a0d..80336cc35 100644 --- a/packages/query-db-collection/src/global.d.ts +++ b/packages/query-db-collection/src/global.d.ts @@ -12,22 +12,21 @@ import type { LoadSubsetOptions } from "@tanstack/db" /** - * 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. + * Base interface for Query Collection meta properties. + * Users can extend this interface to add their own custom properties while + * preserving loadSubsetOptions. * * @example * ```typescript - * declare module "@tanstack/query-core" { - * interface Register { - * queryMeta: import("@tanstack/query-db-collection").QueryCollectionMeta & { - * myCustomProperty: string - * } + * declare module "@tanstack/query-db-collection" { + * interface QueryCollectionMeta { + * myCustomProperty: string + * userId?: number * } * } * ``` */ -export type QueryCollectionMeta = Record & { +export interface QueryCollectionMeta extends Record { loadSubsetOptions: LoadSubsetOptions } diff --git a/packages/query-db-collection/src/index.ts b/packages/query-db-collection/src/index.ts index 9f23731e4..bbb81446b 100644 --- a/packages/query-db-collection/src/index.ts +++ b/packages/query-db-collection/src/index.ts @@ -7,7 +7,7 @@ export { type SyncOperation, } from "./query" -// Export QueryCollectionMeta from global.d.ts +// Export QueryCollectionMeta from global.d.ts (the only source) export type { QueryCollectionMeta } from "./global" export * from "./errors" diff --git a/packages/query-db-collection/tests/query.test-d.ts b/packages/query-db-collection/tests/query.test-d.ts index c5970847b..6b63710c1 100644 --- a/packages/query-db-collection/tests/query.test-d.ts +++ b/packages/query-db-collection/tests/query.test-d.ts @@ -505,5 +505,44 @@ describe(`Query collection type resolution tests`, () => { const options = queryCollectionOptions(config) createCollection(options) }) + + it(`should allow users to extend QueryCollectionMeta via module augmentation`, () => { + // This test validates that users can extend QueryCollectionMeta to add custom properties + // by augmenting the @tanstack/query-db-collection module + + // Simulate user augmentation (normally in their own type definition file) + interface ExtendedMeta { + customUserId: number + customContext?: string + } + + // In reality, users would do: + // declare module "@tanstack/query-db-collection" { + // interface QueryCollectionMeta extends ExtendedMeta {} + // } + + const config: QueryCollectionConfig = { + id: `extendMetaTest`, + queryClient, + queryKey: [`extendMetaTest`], + queryFn: (ctx) => { + // ctx.meta still has loadSubsetOptions + expectTypeOf( + ctx.meta?.loadSubsetOptions + ).toMatchTypeOf() + + // This test documents the extension pattern even though we can't + // actually augment QueryCollectionMeta in a test file (it would + // affect all other tests in the same compilation unit) + + return Promise.resolve([]) + }, + getKey: (item) => item.id, + syncMode: `on-demand`, + } + + const options = queryCollectionOptions(config) + createCollection(options) + }) }) }) From 6b5fb1b0e597ddbe0ff2b8b1218203f520e350a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Nov 2025 19:13:41 +0000 Subject: [PATCH 3/7] chore: run prettier on query.test-d.ts --- packages/query-db-collection/tests/query.test-d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/query-db-collection/tests/query.test-d.ts b/packages/query-db-collection/tests/query.test-d.ts index 6b63710c1..6808a66af 100644 --- a/packages/query-db-collection/tests/query.test-d.ts +++ b/packages/query-db-collection/tests/query.test-d.ts @@ -527,9 +527,9 @@ describe(`Query collection type resolution tests`, () => { queryKey: [`extendMetaTest`], queryFn: (ctx) => { // ctx.meta still has loadSubsetOptions - expectTypeOf( - ctx.meta?.loadSubsetOptions - ).toMatchTypeOf() + expectTypeOf(ctx.meta?.loadSubsetOptions).toMatchTypeOf< + LoadSubsetOptions | undefined + >() // This test documents the extension pattern even though we can't // actually augment QueryCollectionMeta in a test file (it would From 2131f1c4929b80496e0888393de53ae7af9dfd3f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Nov 2025 19:18:56 +0000 Subject: [PATCH 4/7] fix: resolve eslint errors in query-db-collection - Replace triple-slash reference with import type {} from './global' - Remove unused ExtendedMeta interface from test All errors are now resolved, only pre-existing warnings remain. --- packages/query-db-collection/src/index.ts | 3 ++- packages/query-db-collection/tests/query.test-d.ts | 11 ++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/query-db-collection/src/index.ts b/packages/query-db-collection/src/index.ts index bbb81446b..57616c7ae 100644 --- a/packages/query-db-collection/src/index.ts +++ b/packages/query-db-collection/src/index.ts @@ -1,4 +1,5 @@ -/// +// Import global.d.ts to ensure module augmentation is loaded +import type {} from "./global" export { queryCollectionOptions, diff --git a/packages/query-db-collection/tests/query.test-d.ts b/packages/query-db-collection/tests/query.test-d.ts index 6808a66af..b58a543a6 100644 --- a/packages/query-db-collection/tests/query.test-d.ts +++ b/packages/query-db-collection/tests/query.test-d.ts @@ -510,15 +510,12 @@ describe(`Query collection type resolution tests`, () => { // This test validates that users can extend QueryCollectionMeta to add custom properties // by augmenting the @tanstack/query-db-collection module - // Simulate user augmentation (normally in their own type definition file) - interface ExtendedMeta { - customUserId: number - customContext?: string - } - // In reality, users would do: // declare module "@tanstack/query-db-collection" { - // interface QueryCollectionMeta extends ExtendedMeta {} + // interface QueryCollectionMeta { + // customUserId: number + // customContext?: string + // } // } const config: QueryCollectionConfig = { From 0f869c8109e34152aaa17ba141bb69ef8eeb784c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Nov 2025 19:22:13 +0000 Subject: [PATCH 5/7] chore: add changeset for automatic meta type-safety --- .changeset/automatic-meta-type-safety.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/automatic-meta-type-safety.md diff --git a/.changeset/automatic-meta-type-safety.md b/.changeset/automatic-meta-type-safety.md new file mode 100644 index 000000000..18b7632ad --- /dev/null +++ b/.changeset/automatic-meta-type-safety.md @@ -0,0 +1,17 @@ +--- +"@tanstack/query-db-collection": patch +--- + +fix: ensure ctx.meta.loadSubsetOptions type-safety works automatically + +The module augmentation for ctx.meta.loadSubsetOptions is now guaranteed to load automatically when importing from @tanstack/query-db-collection. Previously, users needed to explicitly import QueryCollectionMeta or use @ts-ignore to pass ctx.meta?.loadSubsetOptions to parseLoadSubsetOptions. + +Additionally, QueryCollectionMeta is now an interface (instead of a type alias), enabling users to safely extend meta with custom properties via declaration merging: + +```typescript +declare module "@tanstack/query-db-collection" { + interface QueryCollectionMeta { + myCustomProperty: string + } +} +``` From f634e6fe4621093e6e141aeaa0cf55c886f38cf0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Nov 2025 19:24:58 +0000 Subject: [PATCH 6/7] docs: add section on extending meta with custom properties Added comprehensive documentation explaining: - Automatic type-safety for ctx.meta.loadSubsetOptions - How to extend QueryCollectionMeta with custom properties via module augmentation - Real-world examples including API request context - Important notes about TypeScript declaration merging This aligns with TanStack Query's official approach to typing meta using the Register interface. --- docs/collections/query-collection.md | 120 +++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 4f43082d7..67331e126 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -77,6 +77,126 @@ The `queryCollectionOptions` function accepts the following options: - `onUpdate`: Handler called before update operations - `onDelete`: Handler called before delete operations +## Extending Meta with Custom Properties + +The `meta` option allows you to pass additional metadata to your query function. By default, Query Collections automatically include `loadSubsetOptions` in the meta object, which contains filtering, sorting, and pagination options for on-demand queries. + +### Type-Safe Meta Access + +The `ctx.meta.loadSubsetOptions` property is automatically typed as `LoadSubsetOptions` without requiring any additional imports or type assertions: + +```typescript +import { parseLoadSubsetOptions } from "@tanstack/query-db-collection" + +const collection = createCollection( + queryCollectionOptions({ + queryKey: ["products"], + syncMode: "on-demand", + queryFn: async (ctx) => { + // ✅ Type-safe access - no @ts-ignore needed! + const options = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions) + + // Use the parsed options to fetch only what you need + return api.getProducts(options) + }, + queryClient, + getKey: (item) => item.id, + }) +) +``` + +### Adding Custom Meta Properties + +You can extend the meta type to include your own custom properties using TypeScript's module augmentation: + +```typescript +// In a global type definition file (e.g., types.d.ts or global.d.ts) +declare module "@tanstack/query-db-collection" { + interface QueryCollectionMeta { + // Add your custom properties here + userId?: string + includeDeleted?: boolean + cacheTTL?: number + } +} +``` + +Once you've extended the interface, your custom properties are fully typed throughout your application: + +```typescript +const collection = createCollection( + queryCollectionOptions({ + queryKey: ["todos"], + queryFn: async (ctx) => { + // ✅ Both loadSubsetOptions and custom properties are typed + const { loadSubsetOptions, userId, includeDeleted } = ctx.meta + + return api.getTodos({ + ...parseLoadSubsetOptions(loadSubsetOptions), + userId, + includeDeleted, + }) + }, + queryClient, + getKey: (item) => item.id, + // Pass custom meta alongside Query Collection defaults + meta: { + userId: "user-123", + includeDeleted: false, + }, + }) +) +``` + +### Important Notes + +- The module augmentation pattern follows TanStack Query's official approach for typing meta +- `QueryCollectionMeta` is an interface (not a type alias), enabling proper TypeScript declaration merging +- Your custom properties are merged with the base `loadSubsetOptions` property +- All meta properties must be compatible with `Record` +- The augmentation should be done in a file that's included in your TypeScript compilation + +### Example: API Request Context + +A common use case is passing request context to your query function: + +```typescript +// types.d.ts +declare module "@tanstack/query-db-collection" { + interface QueryCollectionMeta { + authToken?: string + locale?: string + version?: string + } +} + +// collections.ts +const productsCollection = createCollection( + queryCollectionOptions({ + queryKey: ["products"], + queryFn: async (ctx) => { + const { loadSubsetOptions, authToken, locale, version } = ctx.meta + + return api.getProducts({ + ...parseLoadSubsetOptions(loadSubsetOptions), + headers: { + Authorization: `Bearer ${authToken}`, + "Accept-Language": locale, + "API-Version": version, + }, + }) + }, + queryClient, + getKey: (item) => item.id, + meta: { + authToken: session.token, + locale: "en-US", + version: "v1", + }, + }) +) +``` + ## Persistence Handlers You can define handlers that are called when mutations occur. These handlers can persist changes to your backend and control whether the query should refetch after the operation: From 2066a4ed97e69c01375685d84a4d8251c71be15e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 15:16:26 +0000 Subject: [PATCH 7/7] fix: ensure global.ts is emitted in build output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed global.d.ts to global.ts so TypeScript properly emits it as global.d.ts in the dist folder. TypeScript's declaration emit only generates .d.ts files from .ts source files - it does not copy handwritten .d.ts files to the output. The module augmentation for @tanstack/query-core is now guaranteed to load because: 1. global.ts exports QueryCollectionMeta interface 2. index.ts re-exports it: export type { QueryCollectionMeta } from "./global" 3. When TypeScript processes the built index.d.ts, it must load global.d.ts to resolve the export, which triggers processing of the module augmentation This follows TypeScript best practices: - Renamed .d.ts → .ts for proper build emission - Re-export forces TypeScript to process the augmentation file - No reliance on triple-slash references or side-effect imports Updated comments to accurately reflect the mechanism. --- .../query-db-collection/src/{global.d.ts => global.ts} | 5 +++-- packages/query-db-collection/src/index.ts | 8 +++----- 2 files changed, 6 insertions(+), 7 deletions(-) rename packages/query-db-collection/src/{global.d.ts => global.ts} (83%) diff --git a/packages/query-db-collection/src/global.d.ts b/packages/query-db-collection/src/global.ts similarity index 83% rename from packages/query-db-collection/src/global.d.ts rename to packages/query-db-collection/src/global.ts index 80336cc35..efb95561f 100644 --- a/packages/query-db-collection/src/global.d.ts +++ b/packages/query-db-collection/src/global.ts @@ -2,8 +2,9 @@ * Global type augmentation for @tanstack/query-core * * This file ensures the module augmentation is always loaded when the package is imported. - * The index.ts file includes a triple-slash reference to this file, which guarantees - * TypeScript processes it whenever anyone imports from @tanstack/query-db-collection. + * The index.ts file re-exports QueryCollectionMeta from this file, which guarantees + * TypeScript processes this file (and its module augmentation) whenever anyone imports + * from @tanstack/query-db-collection. * * This makes ctx.meta?.loadSubsetOptions automatically type-safe without requiring * users to manually import QueryCollectionMeta. diff --git a/packages/query-db-collection/src/index.ts b/packages/query-db-collection/src/index.ts index 57616c7ae..0ecb54133 100644 --- a/packages/query-db-collection/src/index.ts +++ b/packages/query-db-collection/src/index.ts @@ -1,5 +1,6 @@ -// Import global.d.ts to ensure module augmentation is loaded -import type {} from "./global" +// Export QueryCollectionMeta from global.ts +// This ensures the module augmentation in global.ts is processed by TypeScript +export type { QueryCollectionMeta } from "./global" export { queryCollectionOptions, @@ -8,9 +9,6 @@ export { type SyncOperation, } from "./query" -// Export QueryCollectionMeta from global.d.ts (the only source) -export type { QueryCollectionMeta } from "./global" - export * from "./errors" // Re-export expression helpers from @tanstack/db