Skip to content

feat: add materialize() helper for includes subqueries#1504

Open
kevin-dp wants to merge 2 commits intomainfrom
kevin/includes-materialize
Open

feat: add materialize() helper for includes subqueries#1504
kevin-dp wants to merge 2 commits intomainfrom
kevin/includes-materialize

Conversation

@kevin-dp
Copy link
Copy Markdown
Contributor

Closes #1481.

Summary

  • Adds materialize() — a single helper for materializing includes subqueries onto parent rows. It resolves to Array<T> when the wrapped subquery returns multiple rows, and to T | undefined when the subquery is findOne(). The intent is to spare callers from unwrapping a singleton array whenever they know the child query yields at most one row.
  • toArray() is unchanged — it still always produces Array<T>. materialize() is purely additive.
  • Implementation: extends the existing IncludesMaterialization enum with 'singleton', threads it through the IR / compiler / runtime, and updates the materializeIncludedValue helper to return the first child row (or undefined) for the singleton case. The reactive re-emit machinery routes through that helper, so insert/update/delete/no-match transitions all flow naturally.
  • Type inference: materialize(q.from(...).findOne()) infers T | undefined, materialize(q.from(...)) infers Array<T>. Detected at the type level via a phantom IsSingle flag on MaterializeWrapper that's set from TContext extends SingleResult.
  • materialize() is also rejected inside expressions (coalesce(), eq(), …) with the same kind of error message that toArray() produces.

Example

// Multi-row → Array<Issue>
select(({ p }) => ({
  ...p,
  issues: materialize(
    q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id))
  ),
}))

// Singleton → Project | undefined
select(({ i }) => ({
  ...i,
  project: materialize(
    q.from({ p: projects }).where(({ p }) => eq(p.id, i.projectId)).findOne()
  ),
}))

A changeset will follow in a separate commit.

Test plan

  • New runtime tests in tests/query/includes.test.ts cover singleton attach, missing-match → undefined, insert / update / delete reactivity, two parents sharing a correlation key, multi-row fallback, scalar singleton, and nested materialize (array of singletons).
  • New type tests in tests/query/includes.test-d.ts cover array, singleton (with select / without select / scalar), and a nested array-of-singleton case.
  • Full packages/db test suite passes (1502 tests, 0 type errors).
  • Package builds cleanly.

🤖 Generated with Claude Code

kevin-dp and others added 2 commits April 30, 2026 14:39
materialize() resolves to Array<T> for multi-row subqueries and
T | undefined for findOne() subqueries, so callers don't have to
unwrap a singleton array when the child query returns at most one row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 30, 2026

More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

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

@tanstack/capacitor-db-sqlite-persistence

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

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

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

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

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

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

@tanstack/expo-db-sqlite-persistence

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

@tanstack/node-db-sqlite-persistence

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

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

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 182a968

@github-actions
Copy link
Copy Markdown
Contributor

Size Change: +126 B (+0.11%)

Total Size: 114 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 953 B +34 B (+3.7%)
packages/db/dist/esm/query/builder/index.js 5.3 kB +46 B (+0.88%)
packages/db/dist/esm/query/builder/ref-proxy.js 1.22 kB +19 B (+1.58%)
packages/db/dist/esm/query/live/collection-config-builder.js 7.89 kB +12 B (+0.15%)
ℹ️ 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/evaluators.js 1.62 kB
packages/db/dist/esm/query/compiler/expressions.js 430 B
packages/db/dist/esm/query/compiler/group-by.js 2.69 kB
packages/db/dist/esm/query/compiler/index.js 4.13 kB
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/compiler/select.js 1.11 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/ir.js 829 B
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/live/utils.js 1.64 kB
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 2.9 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

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

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.

feat: toArray() equivalent for findOne subqueries

1 participant