-
Notifications
You must be signed in to change notification settings - Fork 140
Description
- I've validated the bug against the latest version of DB packages
Describe the bug
When using a collection with syncMode: 'on-demand' in a subquery, the filters from the subquery's .where() clauses are not passed to the collection's queryFn via ctx.meta?.loadSubsetOptions. This prevents query-driven sync from working when collections are used in subqueries, forcing the API to load all data instead of filtered subsets.
To Reproduce
Steps to reproduce the behavior:
- Create a collection with
syncMode: 'on-demand'and aqueryFnthat usesparseLoadSubsetOptions:
const ordersCollection = createCollection(
queryCollectionOptions({
syncMode: 'on-demand',
queryKey: ['orders'],
queryFn: async ctx => {
const { filters, sorts, limit } = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions);
console.log('Filters:', filters); // Empty array when used in subquery
console.log('loadSubsetOptions.where:', ctx.meta?.loadSubsetOptions?.where); // undefined
const params = {};
filters.forEach(({ field, operator, value }) => {
// Map filters to API params - never executes when in subquery
});
return api.listOrders(params);
},
})
);- Use the collection in a subquery with filters:
const query = q => {
const prepaidOrder = q
.from({ prepaidOrder: ordersCollection })
.where(({ prepaidOrder }) => gte(prepaidOrder.scheduled_at, today))
.where(({ prepaidOrder }) => eq(prepaidOrder.status, 'queued'))
.orderBy(({ prepaidOrder }) => prepaidOrder.scheduled_at, 'asc');
return q
.from({ charge: chargesCollection })
.fullJoin({ prepaidOrder }, ({ charge, prepaidOrder }) =>
eq(charge?.address_id, prepaidOrder?.address_id)
);
};- Observe that
ctx.meta?.loadSubsetOptions.whereisundefinedandfiltersis an empty array[]
Expected behavior
When a collection is used in a subquery with .where() clauses, those filters should be extracted and passed to the collection's queryFn via ctx.meta?.loadSubsetOptions.where, allowing the API to be called with the correct filter parameters. This should work the same way as when the collection is queried directly.
Actual behavior
ctx.meta?.loadSubsetOptions.whereisundefinedctx.meta?.loadSubsetOptions.orderByisundefinedparseLoadSubsetOptions()returns empty arrays forfiltersandsorts- Only
ctx.meta?.loadSubsetOptions.subscriptionis present (aCollectionSubscriptioninstance) - The
queryFncannot determine what filters to apply, so it either loads all data or applies incorrect defaults
Code Example
import { createCollection, parseLoadSubsetOptions } from '@tanstack/react-db';
import { queryCollectionOptions } from '@tanstack/query-db-collection';
import { gte, eq } from '@tanstack/react-db';
// Collection with on-demand sync
const ordersCollection = createCollection(
queryCollectionOptions({
syncMode: 'on-demand',
queryKey: ['orders'],
getKey: (item) => item.id,
queryFn: async ctx => {
const { filters, sorts, limit } = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions);
// ❌ PROBLEM: When used in subquery, these are all empty/undefined
// filters: []
// sorts: []
// ctx.meta?.loadSubsetOptions.where: undefined
const params = {};
filters.forEach(({ field, operator, value }) => {
// This never runs when collection is in subquery
if (operator === 'gte' && field[field.length - 1] === 'scheduled_at') {
params.scheduled_at_min = value;
}
if (operator === 'eq' && field[field.length - 1] === 'status') {
params.status = value;
}
});
return api.listOrders(params); // Called without filters
},
})
);
// Direct query - WORKS ✅
const directQuery = q =>
q
.from({ order: ordersCollection })
.where(({ order }) => gte(order.scheduled_at, today))
.where(({ order }) => eq(order.status, 'queued'));
// ✅ Filters are passed to queryFn correctly
// Subquery - DOESN'T WORK ❌
const subqueryExample = q => {
const prepaidOrder = q
.from({ prepaidOrder: ordersCollection })
.where(({ prepaidOrder }) => gte(prepaidOrder.scheduled_at, today))
.where(({ prepaidOrder }) => eq(prepaidOrder.status, 'queued'));
return q
.from({ charge: chargesCollection })
.fullJoin({ prepaidOrder }, ({ charge, prepaidOrder }) =>
eq(charge?.address_id, prepaidOrder?.address_id)
);
};
// ❌ Filters are NOT passed to queryFnAdditional context
- Version:
@tanstack/react-db@0.5.11,@tanstack/query-db-collection@1.0.6 - Impact: This prevents efficient query-driven sync for collections used in complex queries with joins, forcing full dataset loads and client-side filtering
- Workaround: Currently loading all data and filtering client-side, which defeats the purpose of on-demand sync for large datasets
- The
subscriptionobject is available inctx.meta?.loadSubsetOptions.subscriptionbut doesn't expose the where expression that could be used to extract filters
Related:
This is related to query-driven sync feature introduced in v0.5. The documentation shows it working for direct queries, but doesn't mention this limitation with subqueries.