From 8a29874d9f8b7fe5a642ca3bda8c2a06ec58a454 Mon Sep 17 00:00:00 2001 From: Dmitrii Troitskii Date: Sun, 8 Mar 2026 18:35:27 +0000 Subject: [PATCH 1/3] fix(db): infer type in coalesce() instead of returning BasicExpression Previously coalesce() always returned BasicExpression, losing type information. Now it uses a generic to infer the non-nullable union of all argument types, matching the semantic of SQL COALESCE. Fixes #1341 --- .changeset/fix-coalesce-type.md | 5 +++++ packages/db/src/query/builder/functions.ts | 6 ++++-- packages/db/tests/query/builder/callback-types.test-d.ts | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-coalesce-type.md diff --git a/.changeset/fix-coalesce-type.md b/.changeset/fix-coalesce-type.md new file mode 100644 index 000000000..c6e8fefd0 --- /dev/null +++ b/.changeset/fix-coalesce-type.md @@ -0,0 +1,5 @@ +--- +"@tanstack/db": patch +--- + +fix(db): infer type in coalesce() instead of returning BasicExpression diff --git a/packages/db/src/query/builder/functions.ts b/packages/db/src/query/builder/functions.ts index 887c1468d..b11aea1fc 100644 --- a/packages/db/src/query/builder/functions.ts +++ b/packages/db/src/query/builder/functions.ts @@ -285,11 +285,13 @@ export function concat( ) } -export function coalesce(...args: Array): BasicExpression { +export function coalesce>( + ...args: T +): BasicExpression>> { return new Func( `coalesce`, args.map((arg) => toExpression(arg)), - ) + ) as BasicExpression>> } export function add( diff --git a/packages/db/tests/query/builder/callback-types.test-d.ts b/packages/db/tests/query/builder/callback-types.test-d.ts index 0e901b7fb..b1565ea32 100644 --- a/packages/db/tests/query/builder/callback-types.test-d.ts +++ b/packages/db/tests/query/builder/callback-types.test-d.ts @@ -150,7 +150,7 @@ describe(`Query Builder Callback Types`, () => { BasicExpression >() expectTypeOf(coalesce(user.name, `Unknown`)).toEqualTypeOf< - BasicExpression + BasicExpression >() return { From afa898cd154876f643cfe0d4eacbb637cf4c382e Mon Sep 17 00:00:00 2001 From: Dmitrii Troitskii Date: Tue, 17 Mar 2026 17:05:54 +0000 Subject: [PATCH 2/3] fix(db): preserve null in coalesce() when no guaranteed non-null arg - Use HasGuaranteedNonNull helper to track whether any arg statically cannot be null/undefined - Return CoalesceArgTypes | null unless a non-null arg guarantees it - Update tests to use spread args (varargs) instead of array - Update changeset description Addresses samwillis review feedback and kevin-dp ExpressionLike concerns --- .changeset/fix-coalesce-type.md | 4 ++- packages/db/src/query/builder/functions.ts | 30 +++++++++++++++++-- .../db/tests/query/builder/functions.test.ts | 2 +- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.changeset/fix-coalesce-type.md b/.changeset/fix-coalesce-type.md index c6e8fefd0..b6e853148 100644 --- a/.changeset/fix-coalesce-type.md +++ b/.changeset/fix-coalesce-type.md @@ -2,4 +2,6 @@ "@tanstack/db": patch --- -fix(db): infer type in coalesce() instead of returning BasicExpression +fix(db): preserve null in coalesce() return type when no guaranteed non-null arg is present + +`coalesce()` was typed as returning `BasicExpression`, losing all type information. The signature now infers types from all arguments via tuple generics, returns the union of non-null arg types, and only removes nullability when at least one argument is statically guaranteed non-null. diff --git a/packages/db/src/query/builder/functions.ts b/packages/db/src/query/builder/functions.ts index b11aea1fc..49f9560dd 100644 --- a/packages/db/src/query/builder/functions.ts +++ b/packages/db/src/query/builder/functions.ts @@ -285,13 +285,37 @@ export function concat( ) } -export function coalesce>( +// Helper type for coalesce: extracts non-nullish value types from all args +type CoalesceArgTypes> = { + [K in keyof T]: NonNullable> +}[number] + +// Whether any arg in the tuple is statically guaranteed non-null (i.e., does not include null | undefined) +type HasGuaranteedNonNull> = { + [K in keyof T]: null extends ExtractType + ? undefined extends ExtractType + ? false + : false + : undefined extends ExtractType + ? false + : true +}[number] extends false + ? false + : true + +// coalesce() return type: union of all non-null arg types; null included unless a guaranteed non-null arg exists +type CoalesceReturnType> = + HasGuaranteedNonNull extends true + ? BasicExpression> + : BasicExpression | null> + +export function coalesce]>( ...args: T -): BasicExpression>> { +): CoalesceReturnType { return new Func( `coalesce`, args.map((arg) => toExpression(arg)), - ) as BasicExpression>> + ) as CoalesceReturnType } export function add( diff --git a/packages/db/tests/query/builder/functions.test.ts b/packages/db/tests/query/builder/functions.test.ts index fb6cb4f35..abc6391c9 100644 --- a/packages/db/tests/query/builder/functions.test.ts +++ b/packages/db/tests/query/builder/functions.test.ts @@ -196,7 +196,7 @@ describe(`QueryBuilder Functions`, () => { .from({ employees: employeesCollection }) .select(({ employees }) => ({ id: employees.id, - name_or_default: coalesce([employees.name, `Unknown`]), + name_or_default: coalesce(employees.name, `Unknown`), })) const builtQuery = getQueryIR(query) From 6e7b05d6ba6fbe27fdb98d86ce34b55d3ef70960 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:07:31 +0000 Subject: [PATCH 3/3] ci: apply automated fixes --- .changeset/fix-coalesce-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fix-coalesce-type.md b/.changeset/fix-coalesce-type.md index b6e853148..f52b10f2d 100644 --- a/.changeset/fix-coalesce-type.md +++ b/.changeset/fix-coalesce-type.md @@ -1,5 +1,5 @@ --- -"@tanstack/db": patch +'@tanstack/db': patch --- fix(db): preserve null in coalesce() return type when no guaranteed non-null arg is present