Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Dec 15, 2025

Summary

Adds detection and helpful error messages when users accidentally use JavaScript operators (||, &&, ??, ?:) in query callbacks. These operators are evaluated at query construction time rather than execution time, causing silent incorrect behavior.

User-visible impact: Developers now get clear, actionable errors when making this common mistake, instead of silently broken queries.


Root Cause

Query callbacks like .select(), .where(), and .having() receive RefProxy objects that record property access paths. When users write:

.select(({ user }) => ({ name: user.name || 'default' }))

The || operator evaluates immediately at construction time. Since user.name is a RefProxy object (truthy), it always returns the RefProxy, ignoring 'default'. The query silently produces unexpected results.

Approach

Two-layer detection strategy:

  1. Static source analysis - Parse callback source via .toString() and detect operator patterns after stripping string literals and comments
  2. Runtime Symbol.toPrimitive trap - Catch when RefProxy is coerced to a primitive (string concatenation, arithmetic)
// Static detection patterns
const JS_OPERATOR_PATTERNS = [
  { pattern: /\|\|/, operator: '||', description: 'logical OR' },
  { pattern: /&&/, operator: '&&', description: 'logical AND' },
  { pattern: /\?\?/, operator: '??', description: 'nullish coalescing' },
  { pattern: /\?[^.?][^:]*:/, operator: '?:', description: 'ternary' },
]

Key Invariants

  • Optional chaining (?.) must NOT trigger false positives
  • Operators inside string literals must NOT trigger false positives
  • Detection runs before callback execution, providing immediate feedback

Non-goals

  • Regex literal handling - Operators in regex literals (/a||b/) will trigger false positives. This is documented as a known limitation since regex literals are rarely used in query callbacks.
  • Comparison operators (===, >, <) - These are handled separately in PR Fix QueryCompilationError with undefined expression type #1082

Trade-offs

Alternative Why not
Runtime-only detection Would only catch primitive coercion, miss ||/&&/?? which return objects
AST parsing Heavy dependency, slower, overkill for pattern matching
Source analysis only Misses edge cases like string concatenation

The dual approach catches the broadest range of mistakes with minimal overhead.


Verification

pnpm test  # All 1818 tests pass

Files Changed

File Change
src/errors.ts Add JavaScriptOperatorInQueryError with helpful message and examples
src/query/builder/ref-proxy.ts Add Symbol.toPrimitive trap, checkCallbackForJsOperators(), operator patterns
src/query/builder/index.ts Call checkCallbackForJsOperators in select(), where(), having()
tests/query/builder/ref-proxy.test.ts Add tests for all operators, string literals, regex edge case
tests/query/scheduler.test.ts Fix existing test to use coalesce() instead of ??
tests/query/live-query-collection.test.ts Fix existing test to use ! assertion instead of ??
packages/db-collection-e2e/.../predicates.suite.ts Fix e2e test to use ! assertion

Adds detection and helpful error messages when users mistakenly use
JavaScript operators (||, &&, ??) in query callbacks. These operators
are evaluated at query construction time, not execution time, causing
silent unexpected behavior.

Changes:
- Add JavaScriptOperatorInQueryError with helpful suggestions
- Add Symbol.toPrimitive trap to RefProxy for primitive coercion
- Add checkCallbackForJsOperators() to detect operators in callbacks
- Integrate checks into select(), where(), and having() methods
- Add comprehensive tests for the new error detection
- Fix existing tests that incorrectly used JS operators
@changeset-bot
Copy link

changeset-bot bot commented Dec 15, 2025

🦋 Changeset detected

Latest commit: 3f71f8e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@tanstack/db Patch
@tanstack/angular-db Patch
@tanstack/electric-db-collection Patch
@tanstack/offline-transactions Patch
@tanstack/powersync-db-collection Patch
@tanstack/query-db-collection Patch
@tanstack/react-db Patch
@tanstack/rxdb-db-collection Patch
@tanstack/solid-db Patch
@tanstack/svelte-db Patch
@tanstack/trailbase-db-collection Patch
@tanstack/vue-db Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 15, 2025

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1021

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@1021

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@1021

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@1021

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1021

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1021

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@1021

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@1021

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1021

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@1021

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@1021

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@1021

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@1021

commit: 3f71f8e

@github-actions
Copy link
Contributor

github-actions bot commented Dec 15, 2025

Size Change: +1.05 kB (+1.17%)

Total Size: 91.1 kB

Filename Size Change
./packages/db/dist/esm/errors.js 4.76 kB +266 B (+5.92%) 🔍
./packages/db/dist/esm/index.js 2.71 kB +19 B (+0.71%)
./packages/db/dist/esm/query/builder/index.js 4.04 kB +28 B (+0.7%)
./packages/db/dist/esm/query/builder/ref-proxy.js 1.7 kB +782 B (+85.28%) 🆘
./packages/db/dist/esm/utils.js 881 B -43 B (-4.65%)
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.17 kB
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.32 kB
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/lifecycle.js 1.67 kB
./packages/db/dist/esm/collection/mutations.js 2.34 kB
./packages/db/dist/esm/collection/state.js 3.46 kB
./packages/db/dist/esm/collection/subscription.js 3.62 kB
./packages/db/dist/esm/collection/sync.js 2.38 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 1.93 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 513 B
./packages/db/dist/esm/local-only.js 837 B
./packages/db/dist/esm/local-storage.js 2.1 kB
./packages/db/dist/esm/optimistic-action.js 359 B
./packages/db/dist/esm/paced-mutations.js 496 B
./packages/db/dist/esm/proxy.js 3.75 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/compiler/evaluators.js 1.35 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.8 kB
./packages/db/dist/esm/query/compiler/index.js 1.96 kB
./packages/db/dist/esm/query/compiler/joins.js 2 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.46 kB
./packages/db/dist/esm/query/compiler/select.js 1.07 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 5.33 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 1.9 kB
./packages/db/dist/esm/query/live/internal.js 130 B
./packages/db/dist/esm/query/optimizer.js 2.56 kB
./packages/db/dist/esm/query/predicate-utils.js 2.97 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 B
./packages/db/dist/esm/scheduler.js 1.3 kB
./packages/db/dist/esm/SortedMap.js 1.3 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 247 B
./packages/db/dist/esm/strategies/queueStrategy.js 428 B
./packages/db/dist/esm/strategies/throttleStrategy.js 246 B
./packages/db/dist/esm/transactions.js 2.9 kB
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 852 B
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

compressed-size-action::db-package-size

@github-actions
Copy link
Contributor

github-actions bot commented Dec 15, 2025

Size Change: 0 B

Total Size: 3.35 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.12 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 431 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

@KyleAMathews KyleAMathews moved this to In Progress in 1.0.0 release Jan 6, 2026
KyleAMathews and others added 5 commits January 6, 2026 09:21
- Add ternary operator (?:) detection to JS operator patterns
- Add tests for ternary detection and regex literal edge case
- Fix e2e test to use coalesce() instead of ??
- Add changeset

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cast coalesce result to string to satisfy lower() type requirement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
LOWER(NULL) LIKE '%x%' is NULL (falsy) in SQL, so coalesce isn't needed
when using OR with a title match that will find results.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@KyleAMathews KyleAMathews moved this from In Progress to Ready for review in 1.0.0 release Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

3 participants