@tanstack/db 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/query/builder/functions.cjs +2 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +2 -0
- package/dist/cjs/query/builder/ref-proxy.cjs +6 -0
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +4 -2
- package/dist/cjs/query/compiler/joins.cjs +5 -0
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +14 -5
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/effect.cjs +1 -1
- package/dist/cjs/query/effect.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +22 -1
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +2 -2
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +2 -0
- package/dist/esm/query/builder/functions.js +2 -0
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +4 -2
- package/dist/esm/query/builder/ref-proxy.js +6 -0
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/joins.js +5 -0
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.js +14 -5
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/effect.js +1 -1
- package/dist/esm/query/effect.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.js +22 -1
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +2 -2
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/package.json +1 -1
- package/skills/db-core/live-queries/SKILL.md +2 -1
- package/src/query/builder/functions.ts +2 -0
- package/src/query/builder/ref-proxy.ts +18 -2
- package/src/query/compiler/joins.ts +8 -0
- package/src/query/compiler/order-by.ts +21 -6
- package/src/query/effect.ts +1 -1
- package/src/query/live/collection-config-builder.ts +29 -1
- package/src/query/live/collection-subscriber.ts +5 -5
|
@@ -284,8 +284,10 @@ export function createRefProxyWithSelected<T extends Record<string, any>>(
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
/**
|
|
287
|
-
* Converts a value to an Expression
|
|
288
|
-
* If it's a RefProxy, creates a
|
|
287
|
+
* Converts a value to an Expression.
|
|
288
|
+
* If it's a RefProxy, creates a PropRef. Throws if the value is a
|
|
289
|
+
* ToArrayWrapper or ConcatToArrayWrapper (these must be used as direct
|
|
290
|
+
* select fields). Otherwise wraps it as a Value.
|
|
289
291
|
*/
|
|
290
292
|
export function toExpression<T = any>(value: T): BasicExpression<T>
|
|
291
293
|
export function toExpression(value: RefProxy<any>): BasicExpression<any>
|
|
@@ -293,6 +295,20 @@ export function toExpression(value: any): BasicExpression<any> {
|
|
|
293
295
|
if (isRefProxy(value)) {
|
|
294
296
|
return new PropRef(value.__path)
|
|
295
297
|
}
|
|
298
|
+
// toArray() and concat(toArray()) must be used as direct select fields, not inside expressions
|
|
299
|
+
if (
|
|
300
|
+
value &&
|
|
301
|
+
typeof value === `object` &&
|
|
302
|
+
(value.__brand === `ToArrayWrapper` ||
|
|
303
|
+
value.__brand === `ConcatToArrayWrapper`)
|
|
304
|
+
) {
|
|
305
|
+
const name =
|
|
306
|
+
value.__brand === `ToArrayWrapper` ? `toArray()` : `concat(toArray())`
|
|
307
|
+
throw new Error(
|
|
308
|
+
`${name} cannot be used inside expressions (e.g., coalesce(), eq(), not()). ` +
|
|
309
|
+
`Use ${name} directly as a select field value instead.`,
|
|
310
|
+
)
|
|
311
|
+
}
|
|
296
312
|
// If it's already an Expression (Func, Ref, Value) or Agg, return it directly
|
|
297
313
|
if (
|
|
298
314
|
value &&
|
|
@@ -324,6 +324,14 @@ function processJoin(
|
|
|
324
324
|
|
|
325
325
|
if (!loaded) {
|
|
326
326
|
// Snapshot wasn't sent because it could not be loaded from the indexes
|
|
327
|
+
const collectionId = followRefCollection.id
|
|
328
|
+
const fieldPath = followRefResult.path.join(`.`)
|
|
329
|
+
console.warn(
|
|
330
|
+
`[TanStack DB]${collectionId ? ` [${collectionId}]` : ``} Join requires an index on "${fieldPath}" for efficient loading. ` +
|
|
331
|
+
`Falling back to loading all data. ` +
|
|
332
|
+
`Consider creating an index on the collection with collection.createIndex((row) => row.${fieldPath}) ` +
|
|
333
|
+
`or enable auto-indexing with autoIndex: 'eager' and a defaultIndexType.`,
|
|
334
|
+
)
|
|
327
335
|
lazySourceSubscription.requestSnapshot()
|
|
328
336
|
}
|
|
329
337
|
}),
|
|
@@ -191,6 +191,17 @@ export function processOrderBy(
|
|
|
191
191
|
index = undefined
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
if (!index) {
|
|
195
|
+
const collectionId = followRefCollection.id
|
|
196
|
+
const fieldPath = followRefResult.path.join(`.`)
|
|
197
|
+
console.warn(
|
|
198
|
+
`[TanStack DB]${collectionId ? ` [${collectionId}]` : ``} orderBy with limit requires an index on "${fieldPath}" for efficient lazy loading. ` +
|
|
199
|
+
`Falling back to loading all data. ` +
|
|
200
|
+
`Consider creating an index on the collection with collection.createIndex((row) => row.${fieldPath}) ` +
|
|
201
|
+
`or enable auto-indexing with autoIndex: 'eager' and a defaultIndexType.`,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
194
205
|
orderByAlias =
|
|
195
206
|
firstOrderByExpression.path.length > 1
|
|
196
207
|
? String(firstOrderByExpression.path[0])
|
|
@@ -292,12 +303,16 @@ export function processOrderBy(
|
|
|
292
303
|
|
|
293
304
|
// Set up lazy loading callback to track how much more data is needed
|
|
294
305
|
// This is used by loadMoreIfNeeded to determine if more data should be loaded
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
306
|
+
// Only enable when an index exists — without an index, lazy loading can't work
|
|
307
|
+
// and all data is loaded eagerly via requestSnapshot instead.
|
|
308
|
+
if (index) {
|
|
309
|
+
setSizeCallback = (getSize: () => number) => {
|
|
310
|
+
optimizableOrderByCollections[targetCollectionId]![`dataNeeded`] =
|
|
311
|
+
() => {
|
|
312
|
+
const size = getSize()
|
|
313
|
+
return Math.max(0, orderByOptimizationInfo!.limit - size)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
301
316
|
}
|
|
302
317
|
}
|
|
303
318
|
}
|
package/src/query/effect.ts
CHANGED
|
@@ -883,7 +883,7 @@ class EffectPipelineRunner<TRow extends object, TKey extends string | number> {
|
|
|
883
883
|
for (const [, orderByInfo] of Object.entries(
|
|
884
884
|
this.optimizableOrderByCollections,
|
|
885
885
|
)) {
|
|
886
|
-
if (!orderByInfo.dataNeeded) continue
|
|
886
|
+
if (!orderByInfo.dataNeeded || !orderByInfo.index) continue
|
|
887
887
|
|
|
888
888
|
if (this.pendingOrderedLoadPromise) {
|
|
889
889
|
// Wait for in-flight loads to complete before requesting more
|
|
@@ -1699,6 +1699,30 @@ function flushIncludesState(
|
|
|
1699
1699
|
)
|
|
1700
1700
|
}
|
|
1701
1701
|
}
|
|
1702
|
+
// Finally: entries with deep nested buffer changes (grandchild-or-deeper buffers
|
|
1703
|
+
// have pending data, but neither this level nor the immediate child level changed).
|
|
1704
|
+
// Without this pass, changes at depth 3+ are stranded because drainNestedBuffers
|
|
1705
|
+
// only drains one level and Phase 4 only flushes entries dirty from Phase 2/3.
|
|
1706
|
+
const deepBufferDirty = new Set<unknown>()
|
|
1707
|
+
if (state.nestedSetups) {
|
|
1708
|
+
for (const [correlationKey, entry] of state.childRegistry) {
|
|
1709
|
+
if (entriesWithChildChanges.has(correlationKey)) continue
|
|
1710
|
+
if (dirtyFromBuffers.has(correlationKey)) continue
|
|
1711
|
+
if (
|
|
1712
|
+
entry.includesStates &&
|
|
1713
|
+
hasPendingIncludesChanges(entry.includesStates)
|
|
1714
|
+
) {
|
|
1715
|
+
flushIncludesState(
|
|
1716
|
+
entry.includesStates,
|
|
1717
|
+
entry.collection,
|
|
1718
|
+
entry.collection.id,
|
|
1719
|
+
null,
|
|
1720
|
+
entry.syncMethods,
|
|
1721
|
+
)
|
|
1722
|
+
deepBufferDirty.add(correlationKey)
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1702
1726
|
|
|
1703
1727
|
// For inline materializations: re-emit affected parents with updated snapshots.
|
|
1704
1728
|
// We mutate items in-place (so collection.get() reflects changes immediately)
|
|
@@ -1707,7 +1731,11 @@ function flushIncludesState(
|
|
|
1707
1731
|
// deepEquals, but in-place mutation means both sides reference the same
|
|
1708
1732
|
// object, so the comparison always returns true and suppresses the event.
|
|
1709
1733
|
const inlineReEmitKeys = materializesInline(state)
|
|
1710
|
-
? new Set([
|
|
1734
|
+
? new Set([
|
|
1735
|
+
...(affectedCorrelationKeys || []),
|
|
1736
|
+
...dirtyFromBuffers,
|
|
1737
|
+
...deepBufferDirty,
|
|
1738
|
+
])
|
|
1711
1739
|
: null
|
|
1712
1740
|
if (parentSyncMethods && inlineReEmitKeys && inlineReEmitKeys.size > 0) {
|
|
1713
1741
|
const events: Array<ChangeMessage<any>> = []
|
|
@@ -332,12 +332,12 @@ export class CollectionSubscriber<
|
|
|
332
332
|
return true
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
const { dataNeeded } = orderByInfo
|
|
335
|
+
const { dataNeeded, index } = orderByInfo
|
|
336
336
|
|
|
337
|
-
if (!dataNeeded) {
|
|
338
|
-
// dataNeeded is not set when there's no index (e.g., non-ref expression
|
|
339
|
-
//
|
|
340
|
-
//
|
|
337
|
+
if (!dataNeeded || !index) {
|
|
338
|
+
// dataNeeded is not set when there's no index (e.g., non-ref expression
|
|
339
|
+
// or auto-indexing is disabled). Without an index, lazy loading can't work —
|
|
340
|
+
// all data was already loaded eagerly via requestSnapshot.
|
|
341
341
|
return true
|
|
342
342
|
}
|
|
343
343
|
|