Skip to content

feat(db): add caseWhen query operator#1536

Merged
samwillis merged 21 commits into
mainfrom
case-when
May 19, 2026
Merged

feat(db): add caseWhen query operator#1536
samwillis merged 21 commits into
mainfrom
case-when

Conversation

@samwillis
Copy link
Copy Markdown
Collaborator

@samwillis samwillis commented May 18, 2026

Summary

  • Adds caseWhen(...) as a unified conditional query helper inspired by SQL CASE WHEN, with scalar expression support in select, where, orderBy, groupBy, having, and equality join operands.
  • Adds projection-valued caseWhen support for select() branch values, including nested projection objects, ref spreads, toArray(...), and Collection includes.
  • Implements guarded include routing so includes inside inactive caseWhen branches are not materialized for that parent row.
  • Documents caseWhen in the live query guide and adds a changeset for the new operator.

Why This Is Useful

caseWhen lets queries express conditional logic without dropping down to functional callbacks, so the query compiler can keep optimizing and incrementally maintaining the result.

A common scalar use case is categorizing rows while preserving literal result types:

const userCategories = createLiveQueryCollection((q) =>
  q
    .from({ user: usersCollection })
    .select(({ user }) => ({
      id: user.id,
      category: caseWhen(
        gt(user.age, 65),
        'senior',
        gt(user.age, 18),
        'adult',
        'minor',
      ),
    }))
)

Because scalar caseWhen is a normal query expression, it can also drive filtering, grouping, sorting, and join operands:

const activeFirst = createLiveQueryCollection((q) =>
  q
    .from({ user: usersCollection })
    .where(({ user }) => caseWhen(eq(user.deleted, true), false, true))
    .orderBy(({ user }) => caseWhen(eq(user.active, true), 0, 1))
    .select(({ user }) => ({
      id: user.id,
      name: user.name,
    }))
)

Projection-valued branches make optional result shapes easier to model. For example, a joined row can project an optional nested object only when the joined source exists:

const usersWithOptionalPost = createLiveQueryCollection((q) =>
  q
    .from({ user: usersCollection })
    .leftJoin({ post: postsCollection }, ({ user, post }) =>
      eq(user.id, post.userId),
    )
    .select(({ user, post }) => ({
      id: user.id,
      post: caseWhen(post, {
        title: post.title,
      }),
    }))
)

It also enables guarded includes, where child collections are materialized only for rows whose branch is active:

const adultProfiles = createLiveQueryCollection((q) =>
  q
    .from({ user: usersCollection })
    .select(({ user }) => ({
      id: user.id,
      adultProfile: caseWhen(gt(user.age, 18), {
        ...user,
        posts: q
          .from({ post: postsCollection })
          .where(({ post }) => eq(post.userId, user.id))
          .select(({ post }) => ({
            title: post.title,
          })),
      }),
    }))
)

Design

caseWhen is one public API with two internal paths selected by branch value shape, not call location:

  • When every branch/default value is expression-like, the builder lowers to a scalar Func('caseWhen', ...). The evaluator walks condition/value pairs left to right, returns the first matching branch, evaluates only the active value branch, and returns null when there is no default.
  • When any branch/default value is select-only, the builder returns a CaseWhenWrapper that is lowered by select() into ConditionalSelect IR. The select compiler evaluates branch conditions lazily and returns undefined when there is no matching projection branch and no default.
  • Includes under ConditionalSelect branches carry guard metadata through include extraction/routing. Parent keys are emitted only when the guard chain matches, which keeps inactive branch includes from loading or attaching.

Full design notes: https://gist.github.com/samwillis/60d8d0d937cf23267d0f8383bea3af92

Test plan

  • pnpm exec vitest --run tests/query/case-when.test.ts tests/query/case-when.test-d.ts from packages/db
  • pnpm test from packages/db
  • pnpm test from the repo root was also run; packages/db passed, but the workspace run failed in unrelated @tanstack/expo-db-sqlite-persistence type-checking for untracked tests/helpers/expo-runtime-node-preload.cjs.

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added caseWhen() conditional expressions for scalar and object projections; usable in selects, filters, grouping, joins, and nested selects.
    • Conditional selects now compile and evaluate with ordered branch/default semantics.
  • Includes / Materialization

    • Guarded includes: nested includes are materialized only when their branch guards match; supports nested result paths for placing included results.
  • Aggregation / Grouping

    • caseWhen() works inside GROUP BY and aggregate-bearing projections.
  • Tests

    • Added comprehensive type and runtime tests covering branching, nested includes, grouping, and joins.

Review Change Stack

Made with Cursor

samwillis and others added 11 commits May 17, 2026 12:13
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a SQL-like caseWhen operator: public API and type inference, ConditionalSelect IR, builder/compiler support (including guarded includes), group-by handling, live-query nested resultPath materialization, traversal updates, and comprehensive tests.

Changes

caseWhen Conditional Expression Feature

Layer / File(s) Summary
caseWhen API and types
packages/db/src/query/builder/functions.ts, packages/db/src/query/builder/types.ts, packages/db/src/query/index.ts
Public caseWhen function and overloads, CaseWhenWrapper runtime wrapper, runtime classification between Func('caseWhen', ...) and CaseWhenWrapper, and type utilities (CaseWhenValue, ResultTypeFromCaseWhen) enabling select-time type inference.
ConditionalSelect IR
packages/db/src/query/ir.ts
New IR types ConditionalSelectBranch and SelectValueExpression, ConditionalSelect class with branches and optional default, and isExpressionLike updated to recognize conditional selects.
Builder: detect and build ConditionalSelect
packages/db/src/query/builder/index.ts, packages/db/src/query/builder/functions.ts
buildNestedSelect detects CaseWhenWrapper and delegates to buildConditionalSelect, which validates args, groups (condition, value) pairs, optionally compiles an explicit default, and returns a ConditionalSelect IR node.
Compiler: evaluate caseWhen and expression validation
packages/db/src/query/compiler/evaluators.ts, packages/db/src/query/builder/ref-proxy.ts, packages/db/src/query/compiler/select.ts
compileFunction supports caseWhen with ordered condition/result evaluation and optional default. Adds isCaseWhenConditionTrue truthiness rules. toExpression now rejects CaseWhenWrapper inside expression contexts. Compiler select helpers compile conditional selects to per-row evaluators.
Group-by: aggregates in conditional selects
packages/db/src/query/compiler/group-by.ts
containsAggregate and extractAndReplaceAggregates extended to traverse ConditionalSelect and nested select objects; added grouped compilation helpers (compileGroupedSelectValue, compileGroupedConditionalSelect, etc.) to evaluate conditional selects against aggregated $selected rows.
Includes compilation with guards
packages/db/src/query/compiler/index.ts
Include extraction now finds IncludesSubquery inside ConditionalSelect branches, records per-include guards and resultPath, compiles guard predicates, emits SKIP_INCLUDE for failing guards, and generalizes include placeholder replacement along nested paths and into conditional branches.
Live query materialization
packages/db/src/query/live/collection-config-builder.ts
IncludesOutputState now carries resultPath array; introduced setNestedValue(target, path, value) helper and updated parent/attach/flush code to write included children to nested paths rather than a single field.
Query traversal utilities
packages/db/src/query/live/utils.ts
extractCollectionsFromQuery and extractCollectionAliases now recurse into ConditionalSelect.branches and defaultValue, centralizing traversal for includes, conditional selects, and nested select objects.
Type tests
packages/db/tests/query/case-when.test-d.ts
Type-level tests validating caseWhen inference across scalar/variadic branches, conditional object projections, nested includes (toArray), collection projections, joins/aliases, and optional/union behaviors.
Runtime tests
packages/db/tests/query/case-when.test.ts
Integration tests covering scalar and variadic branches, short-circuiting, where/orderBy usage, conditional object projections (incl. ref-style spreads), group/join scenarios, negative validation cases, and nested include materialization and fallback behavior.
changeset
.changeset/tender-mugs-hear.md
Patch changeset noting the addition of the caseWhen operator and release notes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A rabbit hops through conditional branches,
Pulling caseWhen into tidy clutches,
Guards and paths align with care,
Nested children placed just where,
Queries bloom — hop, code, and crunch! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature being added: a new caseWhen query operator. It is specific, accurate, and directly reflects the primary change.
Description check ✅ Passed The PR description comprehensively covers the changes, includes detailed explanation of why the feature is useful with concrete examples, explains the design approach, and documents the test plan. It follows the spirit of the template with clear sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch case-when

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 18, 2026

More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@1536

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@1536

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@1536

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@1536

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@1536

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@1536

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@1536

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@1536

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@1536

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: a746f73

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

Size Change: +3.11 kB (+2.72%)

Total Size: 117 kB

📦 View Changed
Filename Size Change
packages/db/dist/esm/index.js 3.01 kB +15 B (+0.5%)
packages/db/dist/esm/query/builder/functions.js 1.37 kB +452 B (+49.18%) 🚨
packages/db/dist/esm/query/builder/index.js 5.54 kB +285 B (+5.43%) 🔍
packages/db/dist/esm/query/builder/ref-proxy.js 1.22 kB +16 B (+1.33%)
packages/db/dist/esm/query/compiler/evaluators.js 1.83 kB +209 B (+12.92%) ⚠️
packages/db/dist/esm/query/compiler/group-by.js 3.54 kB +848 B (+31.5%) 🚨
packages/db/dist/esm/query/compiler/index.js 4.75 kB +618 B (+14.95%) ⚠️
packages/db/dist/esm/query/compiler/select.js 1.4 kB +288 B (+25.92%) 🚨
packages/db/dist/esm/query/ir.js 971 B +142 B (+17.13%) ⚠️
packages/db/dist/esm/query/live/collection-config-builder.js 8.01 kB +131 B (+1.66%)
packages/db/dist/esm/query/live/utils.js 1.75 kB +106 B (+6.47%) 🔍
ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/collection/change-events.js 1.39 kB
packages/db/dist/esm/collection/changes.js 1.38 kB
packages/db/dist/esm/collection/cleanup-queue.js 810 B
packages/db/dist/esm/collection/events.js 434 B
packages/db/dist/esm/collection/index.js 3.61 kB
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 1.69 kB
packages/db/dist/esm/collection/mutations.js 2.47 kB
packages/db/dist/esm/collection/state.js 5.26 kB
packages/db/dist/esm/collection/subscription.js 3.74 kB
packages/db/dist/esm/collection/sync.js 2.88 kB
packages/db/dist/esm/collection/transaction-metadata.js 144 B
packages/db/dist/esm/deferred.js 207 B
packages/db/dist/esm/errors.js 4.92 kB
packages/db/dist/esm/event-emitter.js 748 B
packages/db/dist/esm/indexes/auto-index.js 830 B
packages/db/dist/esm/indexes/base-index.js 729 B
packages/db/dist/esm/indexes/basic-index.js 2.05 kB
packages/db/dist/esm/indexes/btree-index.js 2.17 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 538 B
packages/db/dist/esm/local-only.js 890 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/compiler/expressions.js 430 B
packages/db/dist/esm/query/compiler/joins.js 2.34 kB
packages/db/dist/esm/query/compiler/order-by.js 1.72 kB
packages/db/dist/esm/query/effect.js 4.78 kB
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/live-query-collection.js 360 B
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 1.95 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/optimizer.js 2.62 kB
packages/db/dist/esm/query/predicate-utils.js 2.97 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/subset-dedupe.js 960 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 3.02 kB
packages/db/dist/esm/utils.js 927 B
packages/db/dist/esm/utils/array-utils.js 273 B
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 1.05 kB
packages/db/dist/esm/utils/cursor.js 457 B
packages/db/dist/esm/utils/index-optimization.js 1.54 kB
packages/db/dist/esm/utils/type-guards.js 157 B
packages/db/dist/esm/virtual-props.js 360 B

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

Size Change: 0 B

Total Size: 4.24 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/index.js 249 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
packages/react-db/dist/esm/useLiveQuery.js 1.34 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 567 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
packages/db/tests/query/case-when.test-d.ts (1)

31-49: ⚡ Quick win

Add explicit return types to test helper factories.

Line 31 and Line 41 rely on inferred return types; please annotate both function return types explicitly to keep type tests stable against inference drift.

Proposed diff
-function createUsers() {
+function createUsers(): ReturnType<typeof createCollection<User>> {
   return createCollection(
     mockSyncCollectionOptions<User>({
       id: `case-when-type-users`,
       getKey: (user) => user.id,
       initialData: [],
     }),
   )
 }
 
-function createPosts() {
+function createPosts(): ReturnType<typeof createCollection<Post>> {
   return createCollection(
     mockSyncCollectionOptions<Post>({
       id: `case-when-type-posts`,
       getKey: (post) => post.id,
       initialData: [],
     }),
   )
 }

As per coding guidelines, "**/*.{ts,tsx}: Provide proper type annotations for function return values instead of relying on implicit types" and "Provide precise return types for functions; avoid unknownorany return types unless absolutely necessary".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/query/case-when.test-d.ts` around lines 31 - 49, The helper
factory functions createUsers and createPosts rely on inferred return types; add
explicit return type annotations to both (for example, the concrete collection
type returned by createCollection for User and Post or ReturnType<typeof
createCollection> specialized for each) so the test helpers have stable, precise
return types; update the function signatures for createUsers and createPosts to
include those explicit return types.
packages/db/tests/query/case-when.test.ts (2)

384-583: ⚡ Quick win

Add an explicit empty-include corner-case assertion.

The conditional include materialization tests are strong, but they don’t explicitly assert behavior when the included child query resolves to an empty array ([]) in an active branch.

As per coding guidelines, "**/*.test.{ts,tsx,js,jsx}: Test corner cases including: empty collections, single elements, undefined vs null, resolved promises, race conditions, limit/offset edge cases".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/query/case-when.test.ts` around lines 384 - 583, Add an
assertion that explicitly covers the empty-include corner case by ensuring a
branch that should include child rows returns an empty array when there are no
matching child rows: modify one of the conditional projection tests (e.g., the
tests using createUsersCollection/createPostsCollection and the LiveQuery named
query) to either include a user with no posts or adapt an existing user to have
no posts, call query.preload(), then assert that the included field (postTitles
or profile.posts) for that user is exactly [] using query.toArray (optionally
wrapped by stripVirtualPropsAndSymbols or childRows for Collection includes) so
the test verifies an empty-array include is handled correctly.

59-77: ⚡ Quick win

Replace any in test helpers with safer types.

Both helpers use any for parameters and return types, which removes compile-time type checking even in tests.

Suggested typed rewrite
-function stripVirtualPropsAndSymbols(value: any): any {
+function stripVirtualPropsAndSymbols(value: unknown): unknown {
   if (Array.isArray(value)) {
     return value.map((entry) => stripVirtualPropsAndSymbols(entry))
   }

   if (value && typeof value === `object`) {
-    const out: Record<string, any> = {}
-    for (const [key, entry] of Object.entries(stripVirtualProps(value))) {
+    const out: Record<string, unknown> = {}
+    for (const [key, entry] of Object.entries(stripVirtualProps(value as object))) {
       out[key] = stripVirtualPropsAndSymbols(entry)
     }
     return out
   }

   return value
 }

-function childRows(collection: any): Array<any> {
-  return [...collection.toArray].map((row) => stripVirtualPropsAndSymbols(row))
+function childRows(
+  collection: { toArray: Iterable<unknown> },
+): Array<unknown> {
+  return [...collection.toArray].map((row) => stripVirtualPropsAndSymbols(row))
 }

Per coding guidelines: "**/*.{ts,tsx}: Avoid using any types; use unknown instead when type is truly unknown and provide type guards to narrow safely".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/query/case-when.test.ts` around lines 59 - 77, Replace the
unsafe any usages in the test helpers by typing
stripVirtualPropsAndSymbols(value: unknown): unknown (and arrays as unknown[])
and childRows(collection: { toArray: Iterable<unknown> }): unknown[]; keep the
existing runtime checks (Array.isArray, typeof value === 'object' && value !==
null) as the type guards, change the intermediate out to Record<string,
unknown>, and treat entries/returned values from stripVirtualProps as unknown
before recursing so you avoid any while preserving the current logic in
stripVirtualPropsAndSymbols, childRows, and the use of collection.toArray.
packages/db/src/query/compiler/select.ts (1)

204-245: ⚡ Quick win

Extract isCaseWhenConditionTrue() into a shared helper.

This truthiness rule now exists here and again in compiler/evaluators.ts. If one side changes, scalar caseWhen() and projection caseWhen() will pick different branches for the same row.

As per coding guidelines, "Extract common logic into reusable utility functions when duplicated across multiple places".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/query/compiler/select.ts` around lines 204 - 245, The
truthiness logic in isCaseWhenConditionTrue is duplicated; extract it into a
single shared helper (e.g., export function isCaseWhenConditionTrue from a
new/shared utility module) and update compileConditionalSelect to import and use
that helper instead of its local implementation, then update the other
occurrence in compiler/evaluators.ts (and any caseWhen implementations) to
import the same helper so scalar and projection caseWhen use identical logic;
ensure the helper is exported with the same name and update imports where
isCaseWhenConditionTrue was previously defined or referenced.
packages/db/src/query/compiler/group-by.ts (1)

656-666: ⚡ Quick win

Use proper types from the IR module.

The inline type assertion doesn't reference the actual ConditionalSelect type from the IR module. This creates a maintenance risk if the structure changes.

♻️ Refactor to use imported types

Import ConditionalSelect from the IR module (if not already imported), then refine the type check:

  if (expr.type === `conditionalSelect` && `branches` in expr) {
+   const conditionalExpr = expr as ConditionalSelect
    return (
-     expr.branches as Array<{
-       condition: BasicExpression
-       value: BasicExpression | Aggregate | Select | { type: string }
-     }>
+     conditionalExpr.branches
    ).some(
      (branch) =>
        containsAggregate(branch.condition) || containsAggregate(branch.value),
    )
  }

Note: Ensure ConditionalSelect is imported at the top of the file alongside other IR types.

As per coding guidelines, avoid using any types and provide proper type annotations instead of relying on type assertions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/query/compiler/group-by.ts` around lines 656 - 666, Replace
the inline ad-hoc type assertion for conditional selects with the actual
ConditionalSelect IR type: import ConditionalSelect from the IR module (or add
it to the existing IR import), then narrow expr using that type (e.g., treat
expr as ConditionalSelect after the type check `expr.type ===
'conditionalSelect'`) and access expr.branches typed as
ConditionalSelect['branches'] instead of an anonymous cast; update the import
section at the top of group-by.ts accordingly and adjust the containsAggregate
checks to operate on the properly typed branch elements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/db/src/query/builder/index.ts`:
- Around line 901-905: buildNestedSelect currently calls
buildConditionalSelect(obj, parentAliases) but buildConditionalSelect lowers
branches with buildNestedSelect(args[i * 2 + 1], parentAliases) which loses the
current select key/alias and prevents includes (toArray/IncludesSubquery) from
being produced; update the API so the destination/alias is threaded through:
add/pass a destination identifier (e.g. destKey or current select key) from
buildNestedSelect into buildConditionalSelect and ensure buildConditionalSelect
calls buildNestedSelect(branchValue, parentAliasesWithDest) (or
parentAliases.concat(dest)) when lowering each branch; adjust buildNestedSelect
signature and all call sites (including the initial call sites around
CaseWhenWrapper and the other occurrences noted) so branch values retain the
select alias and are treated as include nodes.

In `@packages/db/src/query/compiler/group-by.ts`:
- Around line 656-666: The conditionalSelect branch checker currently tests only
each branch's condition and value for aggregates but misses the optional
expr.defaultValue; update the logic in group-by.ts (inside the conditionalSelect
handling) to also call containsAggregate(expr.defaultValue) and include that
result in the overall some/any check so that aggregates present only in the
defaultValue are detected; reference the conditionalSelect `expr`, the helper
`containsAggregate`, and the branch structure when making this change.

---

Nitpick comments:
In `@packages/db/src/query/compiler/group-by.ts`:
- Around line 656-666: Replace the inline ad-hoc type assertion for conditional
selects with the actual ConditionalSelect IR type: import ConditionalSelect from
the IR module (or add it to the existing IR import), then narrow expr using that
type (e.g., treat expr as ConditionalSelect after the type check `expr.type ===
'conditionalSelect'`) and access expr.branches typed as
ConditionalSelect['branches'] instead of an anonymous cast; update the import
section at the top of group-by.ts accordingly and adjust the containsAggregate
checks to operate on the properly typed branch elements.

In `@packages/db/src/query/compiler/select.ts`:
- Around line 204-245: The truthiness logic in isCaseWhenConditionTrue is
duplicated; extract it into a single shared helper (e.g., export function
isCaseWhenConditionTrue from a new/shared utility module) and update
compileConditionalSelect to import and use that helper instead of its local
implementation, then update the other occurrence in compiler/evaluators.ts (and
any caseWhen implementations) to import the same helper so scalar and projection
caseWhen use identical logic; ensure the helper is exported with the same name
and update imports where isCaseWhenConditionTrue was previously defined or
referenced.

In `@packages/db/tests/query/case-when.test-d.ts`:
- Around line 31-49: The helper factory functions createUsers and createPosts
rely on inferred return types; add explicit return type annotations to both (for
example, the concrete collection type returned by createCollection for User and
Post or ReturnType<typeof createCollection> specialized for each) so the test
helpers have stable, precise return types; update the function signatures for
createUsers and createPosts to include those explicit return types.

In `@packages/db/tests/query/case-when.test.ts`:
- Around line 384-583: Add an assertion that explicitly covers the empty-include
corner case by ensuring a branch that should include child rows returns an empty
array when there are no matching child rows: modify one of the conditional
projection tests (e.g., the tests using
createUsersCollection/createPostsCollection and the LiveQuery named query) to
either include a user with no posts or adapt an existing user to have no posts,
call query.preload(), then assert that the included field (postTitles or
profile.posts) for that user is exactly [] using query.toArray (optionally
wrapped by stripVirtualPropsAndSymbols or childRows for Collection includes) so
the test verifies an empty-array include is handled correctly.
- Around line 59-77: Replace the unsafe any usages in the test helpers by typing
stripVirtualPropsAndSymbols(value: unknown): unknown (and arrays as unknown[])
and childRows(collection: { toArray: Iterable<unknown> }): unknown[]; keep the
existing runtime checks (Array.isArray, typeof value === 'object' && value !==
null) as the type guards, change the intermediate out to Record<string,
unknown>, and treat entries/returned values from stripVirtualProps as unknown
before recursing so you avoid any while preserving the current logic in
stripVirtualPropsAndSymbols, childRows, and the use of collection.toArray.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 069e5d1e-ffc5-4ecc-b6ca-f024c435c362

📥 Commits

Reviewing files that changed from the base of the PR and between 6d9e4c6 and 17564a4.

📒 Files selected for processing (14)
  • packages/db/src/query/builder/functions.ts
  • packages/db/src/query/builder/index.ts
  • packages/db/src/query/builder/ref-proxy.ts
  • packages/db/src/query/builder/types.ts
  • packages/db/src/query/compiler/evaluators.ts
  • packages/db/src/query/compiler/group-by.ts
  • packages/db/src/query/compiler/index.ts
  • packages/db/src/query/compiler/select.ts
  • packages/db/src/query/index.ts
  • packages/db/src/query/ir.ts
  • packages/db/src/query/live/collection-config-builder.ts
  • packages/db/src/query/live/utils.ts
  • packages/db/tests/query/case-when.test-d.ts
  • packages/db/tests/query/case-when.test.ts

Comment thread packages/db/src/query/builder/index.ts Outdated
Comment thread packages/db/src/query/compiler/group-by.ts Outdated
samwillis and others added 2 commits May 18, 2026 15:02
Thread conditional include destinations through caseWhen branches and tighten aggregate handling for grouped conditional projections.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/db/src/query/compiler/select.ts (1)

231-249: ⚡ Quick win

Extract shared CASE truthiness evaluation.

isCaseWhenConditionTrue() is now duplicated here and in packages/db/src/query/compiler/group-by.ts. Keeping both copies in sync is part of caseWhen() correctness now, so this should live in one shared helper.

As per coding guidelines, Extract common logic into reusable utility functions when duplicated across multiple places.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/query/compiler/select.ts` around lines 231 - 249, Duplicate
CASE truthiness logic exists in isCaseWhenConditionTrue (in select.ts) and in
group-by.ts; extract this logic into a single shared utility (e.g., create a new
helper function name like evaluateCaseTruthiness or keep
isCaseWhenConditionTrue) in a common utilities module and replace both local
definitions with imports from that module, update references in caseWhen and any
callers (select.ts's isCaseWhenConditionTrue and the duplicate in group-by.ts)
to use the shared function, and remove the duplicated implementation so there is
a single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/db/src/query/compiler/group-by.ts`:
- Around line 766-852: The branch conditions/values compiled in
compileGroupedConditionalSelect (and any grouped select/value compilation paths
used by compileGroupedSelectObject) still reference original namespaced refs
(e.g., users.status) but the grouped evaluator runs against a row containing
only group-backed namespaces like $selected/group-key, so refs must be lowered
before compiling; update compileGroupedConditionalSelect to transform
branch.condition and branch.value refs into group-scoped refs (or wrap them)
prior to calling compileExpression/compileGroupedSelectValue (i.e., add a helper
that rewrites BasicExpression PropRef nodes that point to grouped tables into
PropRefs under $selected or the group-key namespace and apply it to each
branch.condition and branch.value), and apply the same lowering when creating
entries in compileGroupedSelectObject for non-aggregate ref expressions so the
compiled functions will read from the reconstructed grouped row rather than
undefined original namespaces.
- Around line 746-759: The branch that handles isNestedSelectObject currently
blindly runs extractAndReplaceAggregates over every property, which converts
IncludesSubquery IR into a plain object and breaks compileGroupedSelectValue's
includesSubquery handling; fix this by detecting IncludesSubquery nodes while
iterating the object (check value.type === 'includesSubquery' or the project's
IncludesSubquery predicate) and preserve them as-is into transformed[key] (skip
calling extractAndReplaceAggregates), otherwise continue extracting aggregates
as before (keep using extractAndReplaceAggregates and merging into
allExtracted), so compileGroupedSelectValue can still match value.type ===
'includesSubquery'.

---

Nitpick comments:
In `@packages/db/src/query/compiler/select.ts`:
- Around line 231-249: Duplicate CASE truthiness logic exists in
isCaseWhenConditionTrue (in select.ts) and in group-by.ts; extract this logic
into a single shared utility (e.g., create a new helper function name like
evaluateCaseTruthiness or keep isCaseWhenConditionTrue) in a common utilities
module and replace both local definitions with imports from that module, update
references in caseWhen and any callers (select.ts's isCaseWhenConditionTrue and
the duplicate in group-by.ts) to use the shared function, and remove the
duplicated implementation so there is a single source of truth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40e73b2a-e80c-481d-930b-d0072bc10f93

📥 Commits

Reviewing files that changed from the base of the PR and between 17564a4 and 901eb6e.

📒 Files selected for processing (6)
  • .changeset/tender-mugs-hear.md
  • packages/db/src/query/builder/functions.ts
  • packages/db/src/query/builder/index.ts
  • packages/db/src/query/compiler/group-by.ts
  • packages/db/src/query/compiler/select.ts
  • packages/db/tests/query/case-when.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/tender-mugs-hear.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/db/tests/query/case-when.test.ts
  • packages/db/src/query/builder/functions.ts

Comment thread packages/db/src/query/compiler/group-by.ts
Comment thread packages/db/src/query/compiler/group-by.ts
Copy link
Copy Markdown
Contributor

@kevin-dp kevin-dp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments.
There's quite some duplication. I found 4 copies of the same function spread across the codebase. There are probably other functions that are duplicated too that i didn't spot. Please have an agent go through it to find all duplicated functions and extract them into a single shared function.

Comment thread packages/db/src/query/builder/functions.ts Outdated
Comment thread packages/db/src/query/builder/types.ts
Comment thread packages/db/src/query/compiler/group-by.ts Outdated
Comment thread packages/db/src/query/compiler/index.ts Outdated
Comment thread packages/db/src/query/compiler/select.ts Outdated
Use one compiler helper for scalar and projection caseWhen branch truthiness so every evaluation path stays consistent.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/db/src/query/compiler/select.ts`:
- Around line 174-180: Replace the bare instanceof ConditionalSelect guard with
a single reusable predicate (e.g. isConditionalSelect(value)) and use that
predicate in both compileSelectValue and the addFromObject branch (the spots
currently using instanceof and the type==='conditionalSelect' check). Implement
isConditionalSelect to return true for either an actual ConditionalSelect
instance or an object with type === 'conditionalSelect' (and keep existing
containsAggregate/compileConditionalSelect logic unchanged). Then update the two
checks to call isConditionalSelect(value) so nested caseWhen values and plain
projection objects are classified consistently.
- Around line 171-188: In compileSelectValue, guard against nullish
SelectValueExpression before accessing its .type: update the early branches (the
ConditionalSelect and ValClass checks remain) and add a nullish check (e.g., if
value == null) that returns the intended literal fallback (null) before the line
that reads value.type; ensure the check is placed before the existing `if
(value.type === 'includesSubquery')` so that calling compileSelectValue with
null or undefined (or caseWhen(..., null)) will return the fallback instead of
throwing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 840c8b4e-2d30-446f-8ea5-84cb6fecdfd2

📥 Commits

Reviewing files that changed from the base of the PR and between 901eb6e and a5b4038.

📒 Files selected for processing (5)
  • packages/db/src/query/builder/types.ts
  • packages/db/src/query/compiler/evaluators.ts
  • packages/db/src/query/compiler/group-by.ts
  • packages/db/src/query/compiler/index.ts
  • packages/db/src/query/compiler/select.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/db/src/query/compiler/evaluators.ts
  • packages/db/src/query/builder/types.ts
  • packages/db/src/query/compiler/index.ts
  • packages/db/src/query/compiler/group-by.ts

Comment thread packages/db/src/query/compiler/select.ts
Comment thread packages/db/src/query/compiler/select.ts Outdated
Keep precise caseWhen typings to five branches to reduce API surface while preserving runtime behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/db/src/query/builder/functions.ts (1)

693-698: ⚡ Quick win

Inconsistent wrapper pattern: use declare and unknown to match sibling wrappers.

CaseWhenWrapper differs from ToArrayWrapper and ConcatToArrayWrapper in two ways:

  1. Default type parameter is any instead of unknown
  2. _result is a real optional property instead of a phantom (declare) property

Without declare, instances will have an actual _result property at runtime (set to undefined), unlike the other wrappers.

♻️ Proposed fix to align with sibling wrappers
-export class CaseWhenWrapper<_T = any> {
+export class CaseWhenWrapper<_T = unknown> {
   readonly __brand = `CaseWhenWrapper` as const
   declare readonly _type: `caseWhen`
-  readonly _result?: _T
+  declare readonly _result: _T
   constructor(public readonly args: Array<CaseWhenValue>) {}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/query/builder/functions.ts` around lines 693 - 698,
CaseWhenWrapper currently uses a default type parameter of any and defines
_result as a real optional property; change it to match the sibling wrappers
(ToArrayWrapper, ConcatToArrayWrapper) by making the generic default unknown
(CaseWhenWrapper<_T = unknown>) and turning _result into a phantom declaration
(declare readonly _result?: _T) so instances do not get a runtime _result
property.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/db/src/query/builder/functions.ts`:
- Around line 693-698: CaseWhenWrapper currently uses a default type parameter
of any and defines _result as a real optional property; change it to match the
sibling wrappers (ToArrayWrapper, ConcatToArrayWrapper) by making the generic
default unknown (CaseWhenWrapper<_T = unknown>) and turning _result into a
phantom declaration (declare readonly _result?: _T) so instances do not get a
runtime _result property.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e312db9d-b579-49b7-9d47-957f41d09575

📥 Commits

Reviewing files that changed from the base of the PR and between a5b4038 and 7ce5c2c.

📒 Files selected for processing (1)
  • packages/db/src/query/builder/functions.ts

samwillis and others added 3 commits May 19, 2026 11:19
Preserve include nodes during aggregate extraction and handle null projection branches without misclassifying conditional selects.

Co-authored-by: Cursor <cursoragent@cursor.com>
Rewrite grouped refs inside aggregate-wrapped conditional projections so branch conditions and values evaluate against grouped keys.

Co-authored-by: Cursor <cursoragent@cursor.com>
Allow longer caseWhen calls without reintroducing verbose precise overloads past five branches.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/db/tests/query/case-when.test-d.ts (1)

91-124: ⚡ Quick win

The fallback-overload type check is currently too weak.

toExtend still passes if fallbackOverload regresses to any, so this case does not actually protect the 6-branch inference path. Please add an exact or explicit non-any assertion around that property before treating this as coverage for the broad overload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/query/case-when.test-d.ts` around lines 91 - 124, The
test's type assertion using toExtend is too permissive and won't fail if
fallbackOverload becomes any; update the assertions to explicitly assert a
non-any exact type for the fallbackOverload property: locate the result variable
(from query.toArray[0]!) and replace or augment the toExtend check with a direct
type equality/non-any check referencing fallbackOverload (e.g., use
expectTypeOf(result.fallbackOverload) toEqualTypeOf or
expectTypeOf(result.fallbackOverload).not.toBeAny()) so the caseWhen six-branch
inference is strictly validated; keep the other OutputWithVirtualKeyed assertion
if desired but ensure fallbackOverload is asserted exactly and not allowed to be
any.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/db/src/query/builder/functions.ts`:
- Around line 753-773: isExpressionValue is currently too permissive and treats
plain projection objects like {type:'val', label: ...} as IR nodes; update
isExpressionValue to validate the full discriminated shape (or a private brand)
rather than only checking 'type'. Concretely, inside isExpressionValue add extra
checks per discriminant: if value.type === 'val' require the actual payload
property (e.g. 'value' or the IR's value-field), if value.type === 'func' ensure
expected fields like 'name' and 'args', if value.type === 'agg' ensure
agg-specific fields, and if value.type === 'ref' ensure reference-specific
fields (or alternatively check for a dedicated internal brand/symbol present on
IR nodes). This change in isExpressionValue (used by caseWhen / CaseWhenWrapper)
will prevent projection objects from being misclassified as IR nodes.
- Around line 570-596: The fallback overload for caseWhen currently returns any
and accepts ...rest: Array<CaseWhenValue>, losing alternating condition/value
typing and the inferred return union; update the signature for the variadic
fallback of caseWhen to use a generic variadic-tuple type (e.g. T extends
readonly [...(ExpressionLike | CaseWhenValue)[]]) that enforces alternating
condition/value entries and compute the resulting return union from those tuple
elements (using conditional/mapped types) instead of any; preserve existing
fixed overloads for 1–5 pairs, then implement the generic overload that
constrains length to an even number and derives the return type, referencing the
caseWhen function, ExpressionLike, and CaseWhenValue so callers with 6+ pairs
keep proper compile-time checks.

---

Nitpick comments:
In `@packages/db/tests/query/case-when.test-d.ts`:
- Around line 91-124: The test's type assertion using toExtend is too permissive
and won't fail if fallbackOverload becomes any; update the assertions to
explicitly assert a non-any exact type for the fallbackOverload property: locate
the result variable (from query.toArray[0]!) and replace or augment the toExtend
check with a direct type equality/non-any check referencing fallbackOverload
(e.g., use expectTypeOf(result.fallbackOverload) toEqualTypeOf or
expectTypeOf(result.fallbackOverload).not.toBeAny()) so the caseWhen six-branch
inference is strictly validated; keep the other OutputWithVirtualKeyed assertion
if desired but ensure fallbackOverload is asserted exactly and not allowed to be
any.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abb29ecd-9fb2-4155-97e4-de2a9b815d1a

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce5c2c and c3ff562.

📒 Files selected for processing (5)
  • packages/db/src/query/builder/functions.ts
  • packages/db/src/query/compiler/group-by.ts
  • packages/db/src/query/compiler/select.ts
  • packages/db/tests/query/case-when.test-d.ts
  • packages/db/tests/query/case-when.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/db/src/query/compiler/select.ts
  • packages/db/tests/query/case-when.test.ts
  • packages/db/src/query/compiler/group-by.ts

Comment thread packages/db/src/query/builder/functions.ts
Comment thread packages/db/src/query/builder/functions.ts
Avoid treating projection objects with type fields as IR expressions when selecting conditional projections.

Co-authored-by: Cursor <cursoragent@cursor.com>
@samwillis samwillis requested a review from kevin-dp May 19, 2026 11:23
Copy link
Copy Markdown
Contributor

@kevin-dp kevin-dp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the comments. The type overload with the var args is great and avoids the need for too many overloads 💯

@samwillis samwillis merged commit 4e9ab39 into main May 19, 2026
13 checks passed
@samwillis samwillis deleted the case-when branch May 19, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants