From 00389a47b258ad58fc3a03c5cc6f66957b9bd2d1 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 17 Jun 2026 22:42:33 +0200 Subject: [PATCH 01/58] fix: safe randomUUID helper for non-secure browser contexts (#1593) * fix(db): use safe randomUUID helper for non-secure browser contexts (#1541) * fix(db-sqlite-persistence-core): use safe randomUUID helper (#1541) * fix(browser-db-sqlite-persistence): use safe randomUUID helper (#1541) * fix(electron-db-sqlite-persistence): use safe randomUUID helper (#1541) * fix(offline-transactions): use safe randomUUID helper (#1541) * ci: apply automated fixes * refactor: rename randomUUID helper to safeRandomUUID and add crypto-undefined test --------- Co-authored-by: Kevin De Porre Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/safe-random-uuid.md | 9 +++ .../src/browser-coordinator.ts | 21 ++--- .../db-sqlite-persistence-core/src/index.ts | 2 + .../src/persisted.ts | 20 +++-- packages/db/src/collection/index.ts | 3 +- packages/db/src/collection/mutations.ts | 7 +- packages/db/src/index.ts | 3 + packages/db/src/local-only.ts | 3 +- packages/db/src/local-storage.ts | 3 +- packages/db/src/transactions.ts | 3 +- packages/db/src/utils/uuid.ts | 45 +++++++++++ packages/db/tests/uuid.test.ts | 76 +++++++++++++++++++ .../src/electron-coordinator.ts | 21 ++--- .../src/OfflineExecutor.ts | 10 ++- .../src/api/OfflineTransaction.ts | 6 +- .../coordination/BroadcastChannelLeader.ts | 3 +- 16 files changed, 193 insertions(+), 42 deletions(-) create mode 100644 .changeset/safe-random-uuid.md create mode 100644 packages/db/src/utils/uuid.ts create mode 100644 packages/db/tests/uuid.test.ts diff --git a/.changeset/safe-random-uuid.md b/.changeset/safe-random-uuid.md new file mode 100644 index 0000000000..a10264d535 --- /dev/null +++ b/.changeset/safe-random-uuid.md @@ -0,0 +1,9 @@ +--- +'@tanstack/db': patch +'@tanstack/browser-db-sqlite-persistence': patch +'@tanstack/offline-transactions': patch +'@tanstack/db-sqlite-persistence-core': patch +'@tanstack/electron-db-sqlite-persistence': patch +--- + +Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. diff --git a/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts b/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts index 5b518ea387..1babddc5a7 100644 --- a/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts +++ b/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '@tanstack/db-sqlite-persistence-core' import type { ApplyLocalMutationsResponse, PersistedCollectionCoordinator, @@ -118,7 +119,7 @@ export type BrowserCollectionCoordinatorOptions = { // --------------------------------------------------------------------------- export class BrowserCollectionCoordinator implements PersistedCollectionCoordinator { - private readonly nodeId = crypto.randomUUID() + private readonly nodeId = safeRandomUUID() private readonly dbName: string private adapter: AdapterWithPullSince | null private readonly channel: BroadcastChannel @@ -205,7 +206,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina error?: string }>(collectionId, { type: `rpc:ensureRemoteSubset:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), options, }) @@ -233,7 +234,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), signature, spec, }) @@ -252,16 +253,16 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina if (this.isLeader(collectionId)) { return this.handleApplyLocalMutations(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } @@ -273,14 +274,14 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina if (this.isLeader(collectionId)) { return this.handlePullSince(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } @@ -663,7 +664,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina // Build and apply the persisted transaction const tx = { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term, seq, rowVersion, diff --git a/packages/db-sqlite-persistence-core/src/index.ts b/packages/db-sqlite-persistence-core/src/index.ts index 921b92f3b0..9e2bb9faae 100644 --- a/packages/db-sqlite-persistence-core/src/index.ts +++ b/packages/db-sqlite-persistence-core/src/index.ts @@ -1,3 +1,5 @@ export * from './persisted' export * from './errors' export * from './sqlite-core-adapter' +// Re-export for use in non-secure browser contexts (see #1541) +export { safeRandomUUID } from '@tanstack/db' diff --git a/packages/db-sqlite-persistence-core/src/persisted.ts b/packages/db-sqlite-persistence-core/src/persisted.ts index 7c20e96883..12cf8319c3 100644 --- a/packages/db-sqlite-persistence-core/src/persisted.ts +++ b/packages/db-sqlite-persistence-core/src/persisted.ts @@ -1,4 +1,8 @@ -import { compileSingleRowExpression, toBooleanPredicate } from '@tanstack/db' +import { + compileSingleRowExpression, + safeRandomUUID, + toBooleanPredicate, +} from '@tanstack/db' import { InvalidPersistedCollectionConfigError, InvalidPersistedCollectionCoordinatorError, @@ -440,7 +444,7 @@ type SyncControlFns = { export class SingleProcessCoordinator implements PersistedCollectionCoordinator { private readonly nodeId: string - constructor(nodeId: string = crypto.randomUUID()) { + constructor(nodeId: string = safeRandomUUID()) { this.nodeId = nodeId } @@ -467,7 +471,7 @@ export class SingleProcessCoordinator implements PersistedCollectionCoordinator public pullSince(): Promise { return Promise.resolve({ type: `rpc:pullSince:res`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), ok: true, latestTerm: 1, latestSeq: 0, @@ -1387,7 +1391,7 @@ class PersistedCollectionRuntime< this.createTxCommittedPayload({ term: streamPosition.term, seq: streamPosition.seq, - txId: crypto.randomUUID(), + txId: safeRandomUUID(), latestRowVersion: streamPosition.rowVersion, changedRows: [], deletedKeys: [], @@ -1427,7 +1431,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -1471,7 +1475,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -2607,7 +2611,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...syncOptions } = options const collectionId = - syncOptions.id ?? `persisted-collection:${crypto.randomUUID()}` + syncOptions.id ?? `persisted-collection:${safeRandomUUID()}` const persistence = resolvePersistenceForCollection( syncOptions.persistence, { @@ -2635,7 +2639,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...localOnlyOptions } = options const collectionId = - localOnlyOptions.id ?? `persisted-collection:${crypto.randomUUID()}` + localOnlyOptions.id ?? `persisted-collection:${safeRandomUUID()}` const persistence = resolvePersistenceForCollection( localOnlyOptions.persistence, { diff --git a/packages/db/src/collection/index.ts b/packages/db/src/collection/index.ts index e51eb998d6..137fd5f595 100644 --- a/packages/db/src/collection/index.ts +++ b/packages/db/src/collection/index.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '../utils/uuid' import { CollectionConfigurationError, CollectionRequiresConfigError, @@ -329,7 +330,7 @@ export class CollectionImpl< if (config.id) { this.id = config.id } else { - this.id = crypto.randomUUID() + this.id = safeRandomUUID() } // Set default values for optional config properties diff --git a/packages/db/src/collection/mutations.ts b/packages/db/src/collection/mutations.ts index 765e409ef6..abfb6693eb 100644 --- a/packages/db/src/collection/mutations.ts +++ b/packages/db/src/collection/mutations.ts @@ -1,4 +1,5 @@ import { withArrayChangeTracking, withChangeTracking } from '../proxy' +import { safeRandomUUID } from '../utils/uuid' import { createTransaction, getActiveTransaction } from '../transactions' import { DeleteKeyNotFoundError, @@ -193,7 +194,7 @@ export class CollectionMutationsManager< const globalKey = this.generateGlobalKey(key, item) const mutation: PendingMutation = { - mutationId: crypto.randomUUID(), + mutationId: safeRandomUUID(), original: {}, modified: validatedData, // Pick the values from validatedData based on what's passed in - this is for cases @@ -366,7 +367,7 @@ export class CollectionMutationsManager< const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem) return { - mutationId: crypto.randomUUID(), + mutationId: safeRandomUUID(), original: originalItem, modified: modifiedItem, // Pick the values from modifiedItem based on what's passed in - this is for cases @@ -497,7 +498,7 @@ export class CollectionMutationsManager< `delete`, CollectionImpl > = { - mutationId: crypto.randomUUID(), + mutationId: safeRandomUUID(), original: this.state.get(key)!, modified: this.state.get(key)!, changes: this.state.get(key)!, diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index ec1e229665..347a3119b5 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -80,6 +80,9 @@ export { type EffectQueryInput, } from './query/effect.js' +// UUID helper (safe in non-secure browser contexts, see #1541) +export { safeRandomUUID } from './utils/uuid.js' + // Re-export some stuff explicitly to ensure the type & value is exported export type { Collection } from './collection/index.js' export { IR } diff --git a/packages/db/src/local-only.ts b/packages/db/src/local-only.ts index d3a0a7f2ca..afcf3c9a76 100644 --- a/packages/db/src/local-only.ts +++ b/packages/db/src/local-only.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from './utils/uuid' import type { BaseCollectionConfig, CollectionConfig, @@ -182,7 +183,7 @@ export function localOnlyCollectionOptions< const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } = config - const collectionId = id ?? crypto.randomUUID() + const collectionId = id ?? safeRandomUUID() // Create the sync configuration with transaction confirmation capability const syncResult = createLocalOnlySync(initialData) diff --git a/packages/db/src/local-storage.ts b/packages/db/src/local-storage.ts index 3060b7ec61..05ad388d7c 100644 --- a/packages/db/src/local-storage.ts +++ b/packages/db/src/local-storage.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from './utils/uuid' import { InvalidStorageDataFormatError, InvalidStorageObjectFormatError, @@ -149,7 +150,7 @@ function validateJsonSerializable( * @returns A unique identifier string for tracking data versions */ function generateUuid(): string { - return crypto.randomUUID() + return safeRandomUUID() } /** diff --git a/packages/db/src/transactions.ts b/packages/db/src/transactions.ts index 84e2bb0d5d..fe2f61c0fd 100644 --- a/packages/db/src/transactions.ts +++ b/packages/db/src/transactions.ts @@ -1,4 +1,5 @@ import { createDeferred } from './deferred' +import { safeRandomUUID } from './utils/uuid' import './duplicate-instance-check' import { MissingMutationFunctionError, @@ -224,7 +225,7 @@ class Transaction> { if (typeof config.mutationFn === `undefined`) { throw new MissingMutationFunctionError() } - this.id = config.id ?? crypto.randomUUID() + this.id = config.id ?? safeRandomUUID() this.mutationFn = config.mutationFn this.state = `pending` this.mutations = [] diff --git a/packages/db/src/utils/uuid.ts b/packages/db/src/utils/uuid.ts new file mode 100644 index 0000000000..45875a459f --- /dev/null +++ b/packages/db/src/utils/uuid.ts @@ -0,0 +1,45 @@ +/** + * Returns a RFC 4122 version 4 UUID. + * + * Prefers `crypto.randomUUID()` when available. In non-secure browser contexts + * (e.g. a dev server accessed via a LAN IP over HTTP) `crypto.randomUUID` is + * `undefined`, so this falls back to building a UUIDv4 from + * `crypto.getRandomValues`. Throws if neither API is available. + * + * See https://github.com/TanStack/db/issues/1541. + */ +export function safeRandomUUID(): string { + const c: Crypto | undefined = + typeof globalThis !== `undefined` ? (globalThis as any).crypto : undefined + + if (c && typeof c.randomUUID === `function`) { + return c.randomUUID() + } + + if (c && typeof c.getRandomValues === `function`) { + const bytes = c.getRandomValues(new Uint8Array(16)) + // Per RFC 4122 §4.4: set version (4) and variant (10xx) bits. + bytes[6] = (bytes[6]! & 0x0f) | 0x40 + bytes[8] = (bytes[8]! & 0x3f) | 0x80 + + const hex: Array = [] + for (let i = 0; i < 16; i++) { + hex.push(bytes[i]!.toString(16).padStart(2, `0`)) + } + return ( + hex.slice(0, 4).join(``) + + `-` + + hex.slice(4, 6).join(``) + + `-` + + hex.slice(6, 8).join(``) + + `-` + + hex.slice(8, 10).join(``) + + `-` + + hex.slice(10, 16).join(``) + ) + } + + throw new Error( + `No secure random number generator available: neither crypto.randomUUID nor crypto.getRandomValues is defined in this environment.`, + ) +} diff --git a/packages/db/tests/uuid.test.ts b/packages/db/tests/uuid.test.ts new file mode 100644 index 0000000000..f6cf577e70 --- /dev/null +++ b/packages/db/tests/uuid.test.ts @@ -0,0 +1,76 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { safeRandomUUID } from '../src/utils/uuid' + +const UUID_V4_REGEX = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + +describe(`safeRandomUUID helper`, () => { + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + it(`delegates to crypto.randomUUID when available`, () => { + const spy = vi + .spyOn(globalThis.crypto, `randomUUID`) + .mockReturnValue(`11111111-2222-4333-8444-555555555555`) + const id = safeRandomUUID() + expect(spy).toHaveBeenCalledTimes(1) + expect(id).toBe(`11111111-2222-4333-8444-555555555555`) + }) + + it(`falls back to getRandomValues when crypto.randomUUID is undefined (non-secure context)`, () => { + // Simulate a non-secure browser context where crypto.randomUUID is unavailable + // but getRandomValues remains. + vi.stubGlobal(`crypto`, { + randomUUID: undefined, + getRandomValues: (arr: Uint8Array) => { + // Deterministic-ish fill so we can verify version/variant bits land + // exactly where they should. + for (let i = 0; i < arr.length; i++) arr[i] = 0xff + return arr + }, + }) + + const id = safeRandomUUID() + expect(id).toMatch(UUID_V4_REGEX) + + // Verify version nibble == 4 and variant nibble in [8,9,a,b] + const versionChar = id[14] + const variantChar = id[19] + expect(versionChar).toBe(`4`) + expect([`8`, `9`, `a`, `b`]).toContain(variantChar) + + // With all bytes 0xff, expect ffffffff-ffff-4fff-bfff-ffffffffffff + expect(id).toBe(`ffffffff-ffff-4fff-bfff-ffffffffffff`) + }) + + it(`produces unique, well-formed UUIDs via the fallback path across many calls`, () => { + vi.stubGlobal(`crypto`, { + randomUUID: undefined, + getRandomValues: (arr: Uint8Array) => { + for (let i = 0; i < arr.length; i++) + arr[i] = Math.floor(Math.random() * 256) + return arr + }, + }) + + const seen = new Set() + for (let i = 0; i < 200; i++) { + const id = safeRandomUUID() + expect(id).toMatch(UUID_V4_REGEX) + seen.add(id) + } + expect(seen.size).toBe(200) + }) + + it(`throws when neither crypto.randomUUID nor crypto.getRandomValues is available`, () => { + vi.stubGlobal(`crypto`, {}) + expect(() => safeRandomUUID()).toThrow(/No secure random number generator/) + }) + + it(`throws when globalThis.crypto is undefined`, () => { + vi.stubGlobal(`crypto`, undefined) + expect(() => safeRandomUUID()).toThrow(/No secure random number generator/) + }) +}) diff --git a/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts b/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts index ea9735e709..a4c6bb7fe8 100644 --- a/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts +++ b/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '@tanstack/db-sqlite-persistence-core' import type { ApplyLocalMutationsResponse, PersistedCollectionCoordinator, @@ -118,7 +119,7 @@ export type ElectronCollectionCoordinatorOptions = { // --------------------------------------------------------------------------- export class ElectronCollectionCoordinator implements PersistedCollectionCoordinator { - private readonly nodeId = crypto.randomUUID() + private readonly nodeId = safeRandomUUID() private readonly dbName: string private adapter: AdapterWithPullSince | null private readonly channel: BroadcastChannel @@ -205,7 +206,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin error?: string }>(collectionId, { type: `rpc:ensureRemoteSubset:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), options, }) @@ -233,7 +234,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), signature, spec, }) @@ -252,16 +253,16 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin if (this.isLeader(collectionId)) { return this.handleApplyLocalMutations(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } @@ -273,14 +274,14 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin if (this.isLeader(collectionId)) { return this.handlePullSince(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } @@ -663,7 +664,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin // Build and apply the persisted transaction const tx = { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term, seq, rowVersion, diff --git a/packages/offline-transactions/src/OfflineExecutor.ts b/packages/offline-transactions/src/OfflineExecutor.ts index 8f443277c2..a6140cfebc 100644 --- a/packages/offline-transactions/src/OfflineExecutor.ts +++ b/packages/offline-transactions/src/OfflineExecutor.ts @@ -1,5 +1,9 @@ // Storage adapters -import { createOptimisticAction, createTransaction } from '@tanstack/db' +import { + createOptimisticAction, + createTransaction, + safeRandomUUID, +} from '@tanstack/db' import { IndexedDBAdapter } from './storage/IndexedDBAdapter' import { LocalStorageAdapter } from './storage/LocalStorageAdapter' @@ -367,7 +371,7 @@ export class OfflineExecutor { mutationFn: (params) => mutationFn({ ...params, - idempotencyKey: options.idempotencyKey || crypto.randomUUID(), + idempotencyKey: options.idempotencyKey || safeRandomUUID(), }), metadata: options.metadata, }) @@ -399,7 +403,7 @@ export class OfflineExecutor { mutationFn({ ...vars, ...params, - idempotencyKey: crypto.randomUUID(), + idempotencyKey: safeRandomUUID(), }), onMutate: options.onMutate, }) diff --git a/packages/offline-transactions/src/api/OfflineTransaction.ts b/packages/offline-transactions/src/api/OfflineTransaction.ts index 32c96fd26a..af13484a75 100644 --- a/packages/offline-transactions/src/api/OfflineTransaction.ts +++ b/packages/offline-transactions/src/api/OfflineTransaction.ts @@ -1,4 +1,4 @@ -import { createTransaction } from '@tanstack/db' +import { createTransaction, safeRandomUUID } from '@tanstack/db' import { NonRetriableError } from '../types' import type { PendingMutation, Transaction } from '@tanstack/db' import type { @@ -23,10 +23,10 @@ export class OfflineTransaction { persistTransaction: (tx: OfflineTransactionType) => Promise, executor: any, ) { - this.offlineId = crypto.randomUUID() + this.offlineId = safeRandomUUID() this.mutationFnName = options.mutationFnName this.autoCommit = options.autoCommit ?? true - this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID() + this.idempotencyKey = options.idempotencyKey ?? safeRandomUUID() this.metadata = options.metadata ?? {} this.persistTransaction = persistTransaction this.executor = executor diff --git a/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts b/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts index ce11a8abc3..7f50d06459 100644 --- a/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts +++ b/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '@tanstack/db' import { BaseLeaderElection } from './LeaderElection' interface LeaderMessage { @@ -19,7 +20,7 @@ export class BroadcastChannelLeader extends BaseLeaderElection { constructor(channelName = `offline-executor-leader`) { super() this.channelName = channelName - this.tabId = crypto.randomUUID() + this.tabId = safeRandomUUID() this.setupChannel() } From 3124c9e49e831a0a012295d4e82a55cfda95f848 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Fri, 12 Jun 2026 16:01:44 +0200 Subject: [PATCH 02/58] Added attachments support. --- packages/powersync-db-collection/package.json | 4 +- .../src/attachments.ts | 181 ++++++++++++++++++ pnpm-lock.yaml | 23 ++- 3 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 packages/powersync-db-collection/src/attachments.ts diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 93444c4f26..66f2af4ae5 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -59,10 +59,10 @@ "p-defer": "^4.0.1" }, "peerDependencies": { - "@powersync/common": "^1.41.0" + "@powersync/common": "^1.54.0" }, "devDependencies": { - "@powersync/common": "1.49.0", + "@powersync/common": "1.54.0", "@powersync/node": "0.18.1", "@types/debug": "^4.1.12", "@vitest/coverage-istanbul": "^3.2.4", diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts new file mode 100644 index 0000000000..9359abfc9e --- /dev/null +++ b/packages/powersync-db-collection/src/attachments.ts @@ -0,0 +1,181 @@ +import { + AttachmentQueue, + AttachmentState, + AttachmentTable, + Schema, +} from '@powersync/common' +import { createTransaction } from '@tanstack/db' +import { PowerSyncTransactor } from './PowerSyncTransactor' + +import type { + AbstractPowerSyncDatabase, + AttachmentData, + AttachmentErrorHandler, + ILogger, + LocalStorageAdapter, + RemoteStorageAdapter, + WatchedAttachmentItem, +} from '@powersync/common' +import type { Collection } from '@tanstack/db' + +type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] + +/** + * This extends the default AttachmentQueue constructor params + * FIXME(powersync) we should export this type from the common SDK. + */ +type TanStackDBAttachmentQueueOptions = { + db: AbstractPowerSyncDatabase + /** + * For TanStack, we want access to the synced TanStackDB collection. + * In order to have the same relational data be set in a single transaction. + * This also allows for joining both TanStackDB collections. + */ + attachmentsCollection: Collection + remoteStorage: RemoteStorageAdapter + localStorage: LocalStorageAdapter + watchAttachments: ( + onUpdate: (attachment: Array) => Promise, + signal: AbortSignal, + ) => void + tableName?: string + logger?: ILogger + syncIntervalMs?: number + syncThrottleDuration?: number + downloadAttachments?: boolean + archivedCacheLimit?: number + errorHandler?: AttachmentErrorHandler +} + +interface SaveFileTanStackOptions { + data: AttachmentData + fileExtension: string + mediaType?: string + metaData?: string + id?: string + /** + * Note that this is called inside a synchronous TanStackDB transaction, + * any mutations made to other collections will be in the same transaction. + */ + updateHook?: (attachment: AttachmentQueueRow) => Promise +} + +interface DeleteFileTanStackOptions { + id: string + updateHook?: (attachment: AttachmentQueueRow) => Promise +} + +const _tmpSchema = new Schema({ + attachments: new AttachmentTable(), +}) + +/** + * A custom extension of the PowerSyncAttachmentQueue for TanStackDB. + */ +export class TanStackDBAttachmentQueue extends AttachmentQueue { + readonly powersync: AbstractPowerSyncDatabase + readonly collection: Collection + + constructor(params: TanStackDBAttachmentQueueOptions) { + super(params) + this.powersync = params.db + this.collection = params.attachmentsCollection + } + + /** + * Saves a file to local storage and queues it for upload to remote storage. + * + * Exposes an `updateHook` option which is called inside a TanStackDB transaction, + * relational associations with the provided attachment ID should be made in this hook. + */ + async saveFileTanStack({ + data, + fileExtension, + mediaType, + metaData, + id, + updateHook, + }: SaveFileTanStackOptions): Promise { + const resolvedId = id ?? (await this.generateAttachmentId()) + const filename = `${resolvedId}.${fileExtension}` + const localUri = this.localStorage.getLocalUri(filename) + const size = await this.localStorage.saveFile(localUri, data) + + const attachment: AttachmentQueueRow = { + id: resolvedId, + filename, + media_type: mediaType ?? null, + local_uri: localUri, + state: AttachmentState.QUEUED_UPLOAD, + has_synced: 0, + size, + timestamp: new Date().getTime(), + meta_data: metaData ?? null, + } + + /** + * We use the attachmentService lock to prevent attachment queue race conditions — specifically, + * it stops the watcher from treating a newly inserted attachment record as one that needs + * to be downloaded. + * */ + await this.withAttachmentContext(async (ctx) => { + const tanStackDBTransaction = createTransaction({ + autoCommit: false, + mutationFn: async ({ transaction }) => { + await new PowerSyncTransactor({ + database: ctx.db, + }).applyTransaction(transaction) + }, + }) + + tanStackDBTransaction.mutate(() => { + this.collection.insert(attachment) + // allow the user to associate values in this transaction + updateHook?.(attachment) + }) + + await tanStackDBTransaction.commit() + }) + + return attachment + } + + /** + * Queues a file for deletion from local and remote storage. + * + * Exposes an `updateHook` option which is called inside a TanStackDB transaction, + * relational associations with the provided attachment ID should be cleaned up in this hook. + */ + async deleteFileTanStack({ + id, + updateHook, + }: DeleteFileTanStackOptions): Promise { + await this.withAttachmentContext(async (ctx) => { + const tanStackDBTransaction = createTransaction({ + autoCommit: false, + mutationFn: async ({ transaction }) => { + await new PowerSyncTransactor({ + database: ctx.db, + }).applyTransaction(transaction) + }, + }) + + tanStackDBTransaction.mutate(() => { + const attachment = this.collection.get(id) + if (!attachment) { + throw new Error(`Attachment with id ${id} not found`) + } + + this.collection.update(id, (draft) => { + draft.state = AttachmentState.QUEUED_DELETE + draft.has_synced = 0 + }) + + // allow the user to associate values in this transaction + updateHook?.(attachment) + }) + + await tanStackDBTransaction.commit() + }) + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad174b46b5..c4562916df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,11 +1338,11 @@ importers: version: 4.0.1 devDependencies: '@powersync/common': - specifier: 1.49.0 - version: 1.49.0 + specifier: 1.54.0 + version: 1.54.0 '@powersync/node': specifier: 0.18.1 - version: 0.18.1(@powersync/common@1.49.0)(better-sqlite3@12.8.0) + version: 0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0) '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -4831,8 +4831,8 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@powersync/common@1.49.0': - resolution: {integrity: sha512-g6uonubvtmtyx8hS/G5trg9LsBvzHY3tAKHiV7SIQV3Xyz9ONM6NNnjDMP2vcLZVmsOSi8x/QJZmy/ig1YtBMg==} + '@powersync/common@1.54.0': + resolution: {integrity: sha512-/gzitw4iQL4UI7ILf7TUzCy/cfbDJGU3/aiN/ciaLtDd2Uts3wYARVKclSW0OJhPPisKCX0E8Ev/iZGQPbTgDA==} '@powersync/node@0.18.1': resolution: {integrity: sha512-fcTICgs61CAEb39xiC7pedYsPgbjUInJ/47dr7RIdnEHpAgjWH8bW95/b70qK1fQUANy9lKBBF3PcmfswVgfCw==} @@ -9372,6 +9372,9 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-logger@1.6.1: + resolution: {integrity: sha512-yTgMCPXVjhmg28CuUH8CKjU+cIKL/G+zTu4Fn4lQxs8mRFH/03QTNvEFngcxfg/gRDiQAOoyCKmMTOm9ayOzXA==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -16480,14 +16483,14 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@powersync/common@1.49.0': + '@powersync/common@1.54.0': dependencies: - async-mutex: 0.5.0 event-iterator: 2.0.0 + js-logger: 1.6.1 - '@powersync/node@0.18.1(@powersync/common@1.49.0)(better-sqlite3@12.8.0)': + '@powersync/node@0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0)': dependencies: - '@powersync/common': 1.49.0 + '@powersync/common': 1.54.0 async-mutex: 0.5.0 bson: 6.10.4 comlink: 4.4.2 @@ -22133,6 +22136,8 @@ snapshots: js-base64@3.7.8: {} + js-logger@1.6.1: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} From d18ab196cd1b1c320b990e47fde5c8044d82082d Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Thu, 18 Jun 2026 16:13:35 +0200 Subject: [PATCH 03/58] Types and tests. --- packages/powersync-db-collection/package.json | 4 +- .../src/attachments.ts | 36 +- packages/powersync-db-collection/src/index.ts | 1 + .../tests/attachments.test.ts | 401 ++++++++++++++++++ pnpm-lock.yaml | 22 +- 5 files changed, 419 insertions(+), 45 deletions(-) create mode 100644 packages/powersync-db-collection/tests/attachments.test.ts diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 66f2af4ae5..9374371824 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -59,10 +59,10 @@ "p-defer": "^4.0.1" }, "peerDependencies": { - "@powersync/common": "^1.54.0" + "@powersync/common": "^1.55.0" }, "devDependencies": { - "@powersync/common": "1.54.0", + "@powersync/common": "1.55.0", "@powersync/node": "0.18.1", "@types/debug": "^4.1.12", "@vitest/coverage-istanbul": "^3.2.4", diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index 9359abfc9e..c3c9d8f677 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -10,44 +10,22 @@ import { PowerSyncTransactor } from './PowerSyncTransactor' import type { AbstractPowerSyncDatabase, AttachmentData, - AttachmentErrorHandler, - ILogger, - LocalStorageAdapter, - RemoteStorageAdapter, - WatchedAttachmentItem, + AttachmentQueueOptions, } from '@powersync/common' import type { Collection } from '@tanstack/db' -type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] +export type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] -/** - * This extends the default AttachmentQueue constructor params - * FIXME(powersync) we should export this type from the common SDK. - */ -type TanStackDBAttachmentQueueOptions = { - db: AbstractPowerSyncDatabase +export type TanStackDBAttachmentQueueOptions = AttachmentQueueOptions & { /** * For TanStack, we want access to the synced TanStackDB collection. * In order to have the same relational data be set in a single transaction. * This also allows for joining both TanStackDB collections. */ - attachmentsCollection: Collection - remoteStorage: RemoteStorageAdapter - localStorage: LocalStorageAdapter - watchAttachments: ( - onUpdate: (attachment: Array) => Promise, - signal: AbortSignal, - ) => void - tableName?: string - logger?: ILogger - syncIntervalMs?: number - syncThrottleDuration?: number - downloadAttachments?: boolean - archivedCacheLimit?: number - errorHandler?: AttachmentErrorHandler + attachmentsCollection: Collection } -interface SaveFileTanStackOptions { +export interface SaveFileTanStackOptions { data: AttachmentData fileExtension: string mediaType?: string @@ -60,7 +38,7 @@ interface SaveFileTanStackOptions { updateHook?: (attachment: AttachmentQueueRow) => Promise } -interface DeleteFileTanStackOptions { +export interface DeleteFileTanStackOptions { id: string updateHook?: (attachment: AttachmentQueueRow) => Promise } @@ -74,7 +52,7 @@ const _tmpSchema = new Schema({ */ export class TanStackDBAttachmentQueue extends AttachmentQueue { readonly powersync: AbstractPowerSyncDatabase - readonly collection: Collection + readonly collection: Collection constructor(params: TanStackDBAttachmentQueueOptions) { super(params) diff --git a/packages/powersync-db-collection/src/index.ts b/packages/powersync-db-collection/src/index.ts index f8d0928056..f96a7a0ee4 100644 --- a/packages/powersync-db-collection/src/index.ts +++ b/packages/powersync-db-collection/src/index.ts @@ -1,3 +1,4 @@ +export * from './attachments' export * from './definitions' export * from './powersync' export * from './PowerSyncTransactor' diff --git a/packages/powersync-db-collection/tests/attachments.test.ts b/packages/powersync-db-collection/tests/attachments.test.ts new file mode 100644 index 0000000000..411c7cd753 --- /dev/null +++ b/packages/powersync-db-collection/tests/attachments.test.ts @@ -0,0 +1,401 @@ +import { randomUUID } from 'node:crypto' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { + AttachmentState, + AttachmentTable, + Schema, + Table, + column, +} from '@powersync/common' +import { NodeFileSystemAdapter, PowerSyncDatabase } from '@powersync/node' +import { + createCollection, + isNull, + liveQueryCollectionOptions, + not, +} from '@tanstack/db' +import { describe, expect, it, onTestFinished, vi } from 'vitest' +import { powerSyncCollectionOptions } from '../src' +import { TanStackDBAttachmentQueue } from '../src/attachments' +import { TEST_DATABASE_IMPLEMENTATION } from './test-db-implementation' +import type { + AttachmentErrorHandler, + RemoteStorageAdapter, + WatchedAttachmentItem, +} from '@powersync/common' + +// A minimal valid 1x1 pixel JPEG used as the remote payload for downloads. +const MOCK_JPEG_U8A = [ + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xd9, +] +const createMockJpegBuffer = (): ArrayBuffer => + new Uint8Array(MOCK_JPEG_U8A).buffer + +const SYNC_INTERVAL_MS = 300 +const WAIT_TIMEOUT = 8000 + +const APP_SCHEMA = new Schema({ + users: new Table({ + name: column.text, + email: column.text, + photo_id: column.text, + }), + attachments: new AttachmentTable(), +}) + +type WatchAttachments = ( + onUpdate: (attachments: Array) => Promise, + signal: AbortSignal, +) => void + +const describePowerSync = TEST_DATABASE_IMPLEMENTATION + ? describe + : describe.skip + +describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { + async function setup() { + const db = new PowerSyncDatabase({ + database: { + dbFilename: `attachments-test-${randomUUID()}.sqlite`, + dbLocation: tmpdir(), + implementation: TEST_DATABASE_IMPLEMENTATION, + }, + schema: APP_SCHEMA, + }) + await db.disconnectAndClear() + + const localStorage = new NodeFileSystemAdapter( + join(tmpdir(), `ps-attachments-${randomUUID()}`), + ) + await localStorage.initialize() + + const uploadFile = vi.fn(() => + Promise.resolve(), + ) + const downloadFile = vi.fn(() => + Promise.resolve(createMockJpegBuffer()), + ) + const deleteFile = vi.fn(() => + Promise.resolve(), + ) + const remoteStorage: RemoteStorageAdapter = { + uploadFile, + downloadFile, + deleteFile, + } + + const attachmentsCollection = createCollection( + powerSyncCollectionOptions({ + database: db, + table: APP_SCHEMA.props.attachments, + }), + ) + const usersCollection = createCollection( + powerSyncCollectionOptions({ + database: db, + table: APP_SCHEMA.props.users, + }), + ) + await Promise.all([ + attachmentsCollection.stateWhenReady(), + usersCollection.stateWhenReady(), + ]) + + onTestFinished(async () => { + attachmentsCollection.cleanup() + usersCollection.cleanup() + await db.disconnectAndClear() + await db.close() + await localStorage.clear().catch(() => {}) + }) + + function createQueue( + overrides: { + watchAttachments?: WatchAttachments + archivedCacheLimit?: number + errorHandler?: AttachmentErrorHandler + remoteStorage?: RemoteStorageAdapter + } = {}, + ) { + const queue = new TanStackDBAttachmentQueue({ + db, + attachmentsCollection, + remoteStorage: overrides.remoteStorage ?? remoteStorage, + localStorage, + watchAttachments: overrides.watchAttachments ?? watchPhotoIds, + syncIntervalMs: SYNC_INTERVAL_MS, + archivedCacheLimit: overrides.archivedCacheLimit ?? 0, + errorHandler: overrides.errorHandler, + }) + onTestFinished(() => queue.stopSync()) + return queue + } + + // Reports every photo_id referenced by the users collection as a watched + // attachment. This mirrors how an application links its domain model to the + // attachment queue using a TanStack DB live query rather than a raw SQL + // watch: the `photo_id IS NOT NULL` filter lives in the query, and each + // change re-emits the full set of referenced ids. + const watchPhotoIdsWith = ( + toItem: (photoId: string) => WatchedAttachmentItem, + ): WatchAttachments => { + return async (onUpdate, signal) => { + const livePhotoIds = createCollection( + liveQueryCollectionOptions({ + query: (q) => + q + .from({ user: usersCollection }) + .where(({ user }) => not(isNull(user.photo_id))) + .select(({ user }) => ({ photo_id: user.photo_id })), + }), + ) + + const emit = () => + void onUpdate( + livePhotoIds.toArray + .map((row) => row.photo_id) + .filter((photoId): photoId is string => photoId != null) + .map(toItem), + ) + + // Emit the current snapshot once ready, then on every change. + await livePhotoIds.stateWhenReady() + emit() + const subscription = livePhotoIds.subscribeChanges(() => emit()) + + signal.addEventListener(`abort`, () => { + subscription.unsubscribe() + livePhotoIds.cleanup() + }) + } + } + + const watchPhotoIds = watchPhotoIdsWith((id) => ({ + id, + fileExtension: `jpg`, + })) + + return { + db, + localStorage, + remoteStorage, + uploadFile, + downloadFile, + deleteFile, + attachmentsCollection, + usersCollection, + createQueue, + watchPhotoIds, + watchPhotoIdsWith, + } + } + + /** Waits until the attachment with `id` reaches the expected state. */ + function waitForState( + collection: { get: (id: string) => TRow | undefined }, + id: string, + state: AttachmentState, + ): Promise { + return vi.waitFor( + () => { + const attachment = collection.get(id) + expect( + (attachment as { state?: AttachmentState } | undefined)?.state, + ).toBe(state) + return attachment! + }, + { timeout: WAIT_TIMEOUT, interval: 50 }, + ) + } + + describe(`saveFileTanStack`, () => { + it(`writes the local file and inserts a QUEUED_UPLOAD row into the collection`, async () => { + const { createQueue, attachmentsCollection, localStorage } = await setup() + const queue = createQueue() + + const data = new Uint8Array(123).fill(42).buffer + const record = await queue.saveFileTanStack({ + data, + fileExtension: `jpg`, + mediaType: `image/jpeg`, + }) + + expect(record.size).toBe(123) + expect(record.state).toBe(AttachmentState.QUEUED_UPLOAD) + expect(record.media_type).toBe(`image/jpeg`) + expect(record.filename).toBe(`${record.id}.jpg`) + expect(record.has_synced).toBe(0) + + // The file should exist on disk at the returned local_uri. + expect(await localStorage.fileExists(record.local_uri)).toBe(true) + + // The row should be reflected in the collection once it syncs back. + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.QUEUED_UPLOAD, + ) + }) + + it(`commits the updateHook mutation atomically with the attachment row`, async () => { + const { createQueue, attachmentsCollection, usersCollection } = + await setup() + const queue = createQueue() + + const userId = randomUUID() + const record = await queue.saveFileTanStack({ + data: createMockJpegBuffer(), + fileExtension: `jpg`, + updateHook: async (attachment) => { + usersCollection.insert({ + id: userId, + name: `steven`, + email: `steven@journeyapps.com`, + photo_id: attachment.id, + }) + }, + }) + + // Both the attachment and the linked user row should appear together. + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.QUEUED_UPLOAD, + ) + await vi.waitFor( + () => { + const user = usersCollection.get(userId) + expect(user?.photo_id).toBe(record.id) + }, + { timeout: WAIT_TIMEOUT, interval: 50 }, + ) + }) + + it(`uploads the saved file and transitions it to SYNCED`, async () => { + const { + createQueue, + attachmentsCollection, + usersCollection, + uploadFile, + } = await setup() + const queue = createQueue() + await queue.startSync() + + const userId = randomUUID() + const record = await queue.saveFileTanStack({ + data: createMockJpegBuffer(), + fileExtension: `jpg`, + updateHook: async (attachment) => { + usersCollection.insert({ + id: userId, + name: `steven`, + email: `steven@journeyapps.com`, + photo_id: attachment.id, + }) + }, + }) + + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.SYNCED, + ) + + expect(uploadFile).toHaveBeenCalled() + const [, uploadedAttachment] = uploadFile.mock.calls[0]! + expect(uploadedAttachment.id).toBe(record.id) + }) + + it(`honours a caller-supplied id`, async () => { + const { createQueue } = await setup() + const queue = createQueue() + + const id = `my-custom-id` + const record = await queue.saveFileTanStack({ + id, + data: createMockJpegBuffer(), + fileExtension: `png`, + }) + + expect(record.id).toBe(id) + expect(record.filename).toBe(`${id}.png`) + }) + }) + + describe(`deleteFileTanStack`, () => { + it(`queues an existing attachment for deletion and removes the local file`, async () => { + const { + createQueue, + attachmentsCollection, + usersCollection, + localStorage, + } = await setup() + const queue = createQueue() + await queue.startSync() + + const userId = randomUUID() + const record = await queue.saveFileTanStack({ + data: createMockJpegBuffer(), + fileExtension: `jpg`, + updateHook: async (attachment) => { + usersCollection.insert({ + id: userId, + name: `steven`, + email: `steven@journeyapps.com`, + photo_id: attachment.id, + }) + }, + }) + + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.SYNCED, + ) + + await queue.deleteFileTanStack({ + id: record.id, + updateHook: async (attachment) => { + usersCollection.update(userId, (draft) => { + if (draft.photo_id === attachment.id) { + draft.photo_id = null + } + }) + }, + }) + + // It should immediately be marked for deletion (and no longer synced). + const queued = attachmentsCollection.get(record.id) + expect(queued?.state).toBe(AttachmentState.QUEUED_DELETE) + expect(queued?.has_synced).toBe(0) + + // The user reference should have been cleared in the same transaction. + expect(usersCollection.get(userId)?.photo_id).toBeNull() + + // Eventually the row and the local file are removed. + await vi.waitFor( + () => expect(attachmentsCollection.get(record.id)).toBeUndefined(), + { timeout: WAIT_TIMEOUT, interval: 50 }, + ) + expect(await localStorage.fileExists(record.local_uri)).toBe(false) + }) + + it(`throws for an unknown id and commits nothing`, async () => { + const { createQueue, attachmentsCollection, usersCollection } = + await setup() + const queue = createQueue() + + const hook = vi.fn() + await expect( + queue.deleteFileTanStack({ id: `does-not-exist`, updateHook: hook }), + ).rejects.toThrow(/not found/i) + + // The failing transaction must not have run the hook or touched state. + expect(hook).not.toHaveBeenCalled() + expect(attachmentsCollection.get(`does-not-exist`)).toBeUndefined() + expect(usersCollection.size).toBe(0) + }) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4562916df..3852ece3ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,11 +1338,11 @@ importers: version: 4.0.1 devDependencies: '@powersync/common': - specifier: 1.54.0 - version: 1.54.0 + specifier: 1.55.0 + version: 1.55.0 '@powersync/node': specifier: 0.18.1 - version: 0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0) + version: 0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0) '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -4831,8 +4831,8 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@powersync/common@1.54.0': - resolution: {integrity: sha512-/gzitw4iQL4UI7ILf7TUzCy/cfbDJGU3/aiN/ciaLtDd2Uts3wYARVKclSW0OJhPPisKCX0E8Ev/iZGQPbTgDA==} + '@powersync/common@1.55.0': + resolution: {integrity: sha512-c9K2Gac9wOB4ijVnQT388g7Yeuh6VrfJZFXaL6Rag9Fp7F8JzAYcrWaULLTdMnzm7VW6b6XT5M2ip5yLS8oSBg==} '@powersync/node@0.18.1': resolution: {integrity: sha512-fcTICgs61CAEb39xiC7pedYsPgbjUInJ/47dr7RIdnEHpAgjWH8bW95/b70qK1fQUANy9lKBBF3PcmfswVgfCw==} @@ -8173,9 +8173,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-iterator@2.0.0: - resolution: {integrity: sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==} - event-reduce-js@5.2.7: resolution: {integrity: sha512-Vi6aIiAmakzx81JAwhw8L988aSX5a3ZqqVjHyZa9xFU6P4oT1IotoDreWtjNlS+fvEnASvyIQT565nmkOtns/Q==} engines: {node: '>=16'} @@ -16483,14 +16480,13 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@powersync/common@1.54.0': + '@powersync/common@1.55.0': dependencies: - event-iterator: 2.0.0 js-logger: 1.6.1 - '@powersync/node@0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0)': + '@powersync/node@0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0)': dependencies: - '@powersync/common': 1.54.0 + '@powersync/common': 1.55.0 async-mutex: 0.5.0 bson: 6.10.4 comlink: 4.4.2 @@ -20580,8 +20576,6 @@ snapshots: etag@1.8.1: {} - event-iterator@2.0.0: {} - event-reduce-js@5.2.7: dependencies: array-push-at-sort-position: 4.0.1 From 2147345236ceee6e73d9fc6c0cdc2385833199fc Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 18 Jun 2026 11:42:39 -0600 Subject: [PATCH 04/58] Export and document subtract, multiply, divide math functions (#1151) * feat(db): add subtract, multiply, divide math functions Add missing math functions that were implemented in evaluators but not exported. These enable computed columns in orderBy for ranking algorithms like HN-style scoring that balances recency and rating. - Add subtract(a, b) function - Add multiply(a, b) function - Add divide(a, b) function (with null on divide-by-zero) - Export from query/index.ts - Add to operators list - Add comprehensive tests including orderBy usage * docs: document subtract, multiply, divide math functions - Add documentation for new math functions in live-queries.md - Include example of computed columns in orderBy for ranking algorithms - Add changeset for the new minor feature * ci: apply automated fixes * fix: align math function return types * ci: apply automated fixes * docs: clarify ranking snapshot semantics --------- Co-authored-by: Claude Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/add-math-functions.md | 26 +++++ docs/guides/live-queries.md | 48 ++++++++++ packages/db/src/query/builder/functions.ts | 68 ++++++++------ packages/db/src/query/index.ts | 3 + .../db/tests/query/builder/functions.test.ts | 94 ++++++++++++++++++- 5 files changed, 211 insertions(+), 28 deletions(-) create mode 100644 .changeset/add-math-functions.md diff --git a/.changeset/add-math-functions.md b/.changeset/add-math-functions.md new file mode 100644 index 0000000000..c4e70d0c5d --- /dev/null +++ b/.changeset/add-math-functions.md @@ -0,0 +1,26 @@ +--- +'@tanstack/db': patch +--- + +Add `subtract`, `multiply`, and `divide` math functions for computed columns + +These functions enable complex calculations in `select` and `orderBy` clauses, such as ranking algorithms that combine multiple factors (e.g., HN-style scoring that balances recency and rating). + +```ts +import { subtract, multiply, divide } from '@tanstack/db' + +// Example: Sort by computed ranking score +const ranked = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => + subtract(multiply(r.rating, r.timesMade), divide(r.ageInMs, 86400000)), + 'desc', + ), +) +``` + +- `subtract(a, b)` - Subtraction +- `multiply(a, b)` - Multiplication +- `divide(a, b)` - Division (returns `null` on divide-by-zero) diff --git a/docs/guides/live-queries.md b/docs/guides/live-queries.md index 0780871a3b..988628261a 100644 --- a/docs/guides/live-queries.md +++ b/docs/guides/live-queries.md @@ -2551,6 +2551,54 @@ Add two numbers: add(user.salary, user.bonus) ``` +#### `subtract(left, right)` +Subtract two numbers: +```ts +subtract(user.salary, user.deductions) +``` + +#### `multiply(left, right)` +Multiply two numbers: +```ts +multiply(item.price, item.quantity) +``` + +#### `divide(left, right)` +Divide two numbers (returns `null` on divide-by-zero): +```ts +divide(order.total, order.itemCount) +``` + +#### Computed Columns in orderBy + +You can use math functions directly in `orderBy` to sort by computed values. This is useful for ranking algorithms that combine multiple factors: + +```ts +import { subtract, multiply, divide } from '@tanstack/db' + +// HN-style ranking: balance rating with recency +// Date.now() is captured when this query is created. Recreate the query if +// you need the recency score to advance as time passes. +const rankedRecipes = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => + subtract( + multiply(r.rating, r.timesMade), // weighted rating + divide( + subtract(Date.now(), r.lastMadeAt), // time since last made + 3600000 * 24 // convert ms to days + ) + ), + 'desc' + ) + .limit(20) +) +``` + +> **Note:** When using computed expressions in `orderBy` with `limit()`, lazy loading optimization is skipped (all matching data is loaded first, then sorted). For large collections where this matters, consider pre-computing the ranking score as a stored field. + ### Utility Functions #### `coalesce(...values)` diff --git a/packages/db/src/query/builder/functions.ts b/packages/db/src/query/builder/functions.ts index 47e190162a..84c26e5ca4 100644 --- a/packages/db/src/query/builder/functions.ts +++ b/packages/db/src/query/builder/functions.ts @@ -125,31 +125,12 @@ type MapToNumber = T extends string | Array ? null : T -// Helper type for binary numeric operations (combines nullability of both operands) -type BinaryNumericReturnType = - ExtractType extends infer U1 - ? ExtractType extends infer U2 - ? U1 extends number - ? U2 extends number - ? BasicExpression - : U2 extends number | undefined - ? BasicExpression - : U2 extends number | null - ? BasicExpression - : BasicExpression - : U1 extends number | undefined - ? U2 extends number - ? BasicExpression - : U2 extends number | undefined - ? BasicExpression - : BasicExpression - : U1 extends number | null - ? U2 extends number - ? BasicExpression - : BasicExpression - : BasicExpression - : BasicExpression - : BasicExpression +// Helper type for binary numeric operations. +// Runtime coalesces nullish operands to 0 for these operations, so nullable +// operands don't make the result nullable. +type BinaryNumericReturnType = BasicExpression + +type DivideReturnType = BasicExpression // Operators @@ -620,11 +601,41 @@ export function caseWhen(...args: Array): any { export function add( left: T1, right: T2, -): BinaryNumericReturnType { +): BinaryNumericReturnType { return new Func(`add`, [ toExpression(left), toExpression(right), - ]) as BinaryNumericReturnType + ]) as BinaryNumericReturnType +} + +export function subtract( + left: T1, + right: T2, +): BinaryNumericReturnType { + return new Func(`subtract`, [ + toExpression(left), + toExpression(right), + ]) as BinaryNumericReturnType +} + +export function multiply( + left: T1, + right: T2, +): BinaryNumericReturnType { + return new Func(`multiply`, [ + toExpression(left), + toExpression(right), + ]) as BinaryNumericReturnType +} + +export function divide( + left: T1, + right: T2, +): DivideReturnType { + return new Func(`divide`, [ + toExpression(left), + toExpression(right), + ]) as DivideReturnType } // Aggregates @@ -690,6 +701,9 @@ export const operators = [ `concat`, // Numeric functions `add`, + `subtract`, + `multiply`, + `divide`, // Utility functions `coalesce`, `caseWhen`, diff --git a/packages/db/src/query/index.ts b/packages/db/src/query/index.ts index e23475bc25..8cda812b3f 100644 --- a/packages/db/src/query/index.ts +++ b/packages/db/src/query/index.ts @@ -59,6 +59,9 @@ export { coalesce, caseWhen, add, + subtract, + multiply, + divide, // Aggregates count, avg, diff --git a/packages/db/tests/query/builder/functions.test.ts b/packages/db/tests/query/builder/functions.test.ts index cd40ce2e4c..e4d284960b 100644 --- a/packages/db/tests/query/builder/functions.test.ts +++ b/packages/db/tests/query/builder/functions.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, expectTypeOf, it } from 'vitest' import { CollectionImpl } from '../../../src/collection/index.js' import { Query, getQueryIR } from '../../../src/query/builder/index.js' import { @@ -12,6 +12,7 @@ import { coalesce, concat, count, + divide, eq, gt, gte, @@ -23,12 +24,16 @@ import { lte, max, min, + multiply, not, or, + subtract, sum, toArray, upper, } from '../../../src/query/builder/functions.js' +import { compileSingleRowExpression } from '../../../src/query/compiler/evaluators.js' +import type { BasicExpression } from '../../../src/query/ir.js' // Test schema interface Employee { @@ -324,5 +329,92 @@ describe(`QueryBuilder Functions`, () => { const select = builtQuery.select! expect((select.salary_plus_bonus as any).name).toBe(`add`) }) + + it(`subtract function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + salary_minus_tax: subtract(employees.salary, 5000), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.salary_minus_tax as any).name).toBe(`subtract`) + }) + + it(`multiply function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + double_salary: multiply(employees.salary, 2), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.double_salary as any).name).toBe(`multiply`) + }) + + it(`divide function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + monthly_salary: divide(employees.salary, 12), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.monthly_salary as any).name).toBe(`divide`) + }) + + it(`math functions can be combined for complex calculations`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + // (salary * 1.1) - 500 = 10% raise minus deductions + adjusted_salary: subtract(multiply(employees.salary, 1.1), 500), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.adjusted_salary as any).name).toBe(`subtract`) + }) + + it(`RED review: nullish operands are coalesced to 0 at runtime but widen types`, () => { + const subtractExpression = subtract(10, null) + expectTypeOf(subtractExpression).toEqualTypeOf>() + + const subtractResult = compileSingleRowExpression(subtractExpression)({}) + expect(subtractResult).toBe(10) + }) + + it(`RED review: divide can return null for non-null operand types`, () => { + const divideExpression = divide(10, 0) + expectTypeOf(divideExpression).toEqualTypeOf< + BasicExpression + >() + + const divideResult = compileSingleRowExpression(divideExpression)({}) + expect(divideResult).toBeNull() + }) + + it(`math functions can be used in orderBy`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .orderBy(({ employees }) => multiply(employees.salary, 2), `desc`) + .select(({ employees }) => ({ + id: employees.id, + salary: employees.salary, + })) + + const builtQuery = getQueryIR(query) + expect(builtQuery.orderBy).toBeDefined() + expect(builtQuery.orderBy).toHaveLength(1) + expect((builtQuery.orderBy![0]!.expression as any).name).toBe(`multiply`) + expect(builtQuery.orderBy![0]!.compareOptions.direction).toBe(`desc`) + }) }) }) From 9a5e8e5445435c59ede20d3080d09b95355a67dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:31:28 -0600 Subject: [PATCH 05/58] ci: Version Packages (#1596) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/add-math-functions.md | 26 ------- .changeset/safe-random-uuid.md | 9 --- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 9 +++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 9 +++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 32 +++++++++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 9 +++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 9 +++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 60 files changed, 296 insertions(+), 127 deletions(-) delete mode 100644 .changeset/add-math-functions.md delete mode 100644 .changeset/safe-random-uuid.md diff --git a/.changeset/add-math-functions.md b/.changeset/add-math-functions.md deleted file mode 100644 index c4e70d0c5d..0000000000 --- a/.changeset/add-math-functions.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Add `subtract`, `multiply`, and `divide` math functions for computed columns - -These functions enable complex calculations in `select` and `orderBy` clauses, such as ranking algorithms that combine multiple factors (e.g., HN-style scoring that balances recency and rating). - -```ts -import { subtract, multiply, divide } from '@tanstack/db' - -// Example: Sort by computed ranking score -const ranked = createLiveQueryCollection((q) => - q - .from({ r: recipesCollection }) - .orderBy( - ({ r }) => - subtract(multiply(r.rating, r.timesMade), divide(r.ageInMs, 86400000)), - 'desc', - ), -) -``` - -- `subtract(a, b)` - Subtraction -- `multiply(a, b)` - Multiplication -- `divide(a, b)` - Division (returns `null` on divide-by-zero) diff --git a/.changeset/safe-random-uuid.md b/.changeset/safe-random-uuid.md deleted file mode 100644 index a10264d535..0000000000 --- a/.changeset/safe-random-uuid.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@tanstack/db': patch -'@tanstack/browser-db-sqlite-persistence': patch -'@tanstack/offline-transactions': patch -'@tanstack/db-sqlite-persistence-core': patch -'@tanstack/electron-db-sqlite-persistence': patch ---- - -Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index fd4d3ac679..b4c64bfacc 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.68", - "@tanstack/db": "^0.6.8", + "@tanstack/angular-db": "^0.1.69", + "@tanstack/db": "^0.6.9", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index b15ad88932..ebe72a04c5 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.12", - "@tanstack/node-db-sqlite-persistence": "^0.2.0", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/electron-db-sqlite-persistence": "^0.1.13", + "@tanstack/node-db-sqlite-persistence": "^0.2.1", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 6274e7353c..82b07e3c31 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.8", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.0", + "@tanstack/db": "^0.6.9", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index be14aed669..8db8659ca3 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.8", - "@tanstack/electric-db-collection": "^0.3.6", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/react-db": "^0.1.86", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.0", + "@tanstack/db": "^0.6.9", + "@tanstack/electric-db-collection": "^0.3.7", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/react-db": "^0.1.87", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index 05ce1caea1..eb798b2b86 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.0", - "@tanstack/db": "^0.6.8", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/browser-db-sqlite-persistence": "^0.2.1", + "@tanstack/db": "^0.6.9", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 02a929269c..290ed30adc 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.8", - "@tanstack/react-db": "^0.1.86", + "@tanstack/db": "^0.6.9", + "@tanstack/react-db": "^0.1.87", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 1f0178c52e..2f68958d4d 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index a656ae38ed..9145e3ff5f 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.6", + "@tanstack/electric-db-collection": "^0.3.7", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.86", + "@tanstack/trailbase-db-collection": "^0.1.87", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index 215c98e075..efdec9ece8 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.6", + "@tanstack/electric-db-collection": "^0.3.7", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/solid-db": "^0.2.22", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/solid-db": "^0.2.23", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.86", + "@tanstack/trailbase-db-collection": "^0.1.87", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 10a322326a..cc5e8b4e34 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.69 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.68 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index e8ee4b5620..3e7a47e0c9 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.68", + "version": "0.1.69", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index fafcb0bb1f..5dcf408756 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 8e94ebedf9..30065efe96 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 127512e3aa..2c238a9a83 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index 118201f804..fa47e3e79f 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.13 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + - @tanstack/capacitor-db-sqlite-persistence@0.2.1 + ## 0.0.12 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 7c8cacdf76..90872c4797 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.12", + "version": "0.0.13", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 8ea6d338f6..2ccf6f8fbc 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index 87f4d18c83..f0b263dcf8 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 257183d06e..4394bb569a 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index e1721d2e3a..68b5463537 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.1 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.2.0 ### Minor Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index ddcd5847d0..076abc5a81 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.0", + "version": "0.2.1", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index d59f3991e6..2c8ff69a7f 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,37 @@ # @tanstack/db +## 0.6.9 + +### Patch Changes + +- Add `subtract`, `multiply`, and `divide` math functions for computed columns ([#1151](https://github.com/TanStack/db/pull/1151)) + + These functions enable complex calculations in `select` and `orderBy` clauses, such as ranking algorithms that combine multiple factors (e.g., HN-style scoring that balances recency and rating). + + ```ts + import { subtract, multiply, divide } from '@tanstack/db' + + // Example: Sort by computed ranking score + const ranked = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => + subtract( + multiply(r.rating, r.timesMade), + divide(r.ageInMs, 86400000), + ), + 'desc', + ), + ) + ``` + + - `subtract(a, b)` - Subtraction + - `multiply(a, b)` - Multiplication + - `divide(a, b)` - Division (returns `null` on divide-by-zero) + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + ## 0.6.8 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 218beb8072..7939c67c55 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.8", + "version": "0.6.9", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index d8e0ee03db..3c670daf9d 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.7 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.3.6 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index a265310672..db931807d7 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.6", + "version": "0.3.7", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 9a74f0ce27..73de7a2e6d 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.13 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.1.12 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 18f38372f7..b342835f7b 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.12", + "version": "0.1.13", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index b1cded9a2c..fcab9b256e 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index 935e723540..4750ae2ba7 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.13 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + - @tanstack/expo-db-sqlite-persistence@0.2.1 + ## 0.0.12 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 8922e1a5c6..01e45379d0 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.12", + "version": "0.0.13", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 7ff8f3554c..54eab71556 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index b427f31a5e..fbae0befa1 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index 4391f7b071..386f79f795 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index 2057952ea4..4973473334 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/offline-transactions +## 1.0.34 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 1.0.33 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index 872a47b924..1020be51bc 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.33", + "version": "1.0.34", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index b5447bdea9..b24bb05e72 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.47 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.46 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 93444c4f26..a13faca7f0 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.46", + "version": "0.1.47", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index c64cc6fe15..58727ae34b 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.41 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 1.0.40 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index f3599c7a5e..9a0b654121 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.40", + "version": "1.0.41", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index d8d077ea50..e12c46b786 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.87 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.86 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 65f6f2f315..2ac29a8fa4 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.86", + "version": "0.1.87", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 4f72cb892d..bd6a90668c 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index 36fb14352c..b59c3b5b49 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 2e6f39e24f..dcd6edd405 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.75 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.74 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index edd6a3474b..06a259ecfb 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.74", + "version": "0.1.75", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 28f10b3eff..63d736f91c 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.23 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.2.22 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index 50debdc474..d5d65a6bfc 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.22", + "version": "0.2.23", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index a740729e17..388208fbc7 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.86 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.85 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index 79cfab6a16..e7da54fe70 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.85", + "version": "0.1.86", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 25f620e40b..47726fa7ad 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 254ae4ded8..2279f773b7 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.13 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + - @tanstack/tauri-db-sqlite-persistence@0.2.1 + ## 0.0.12 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 795d1406cd..6d568d4c41 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.12", + "version": "0.0.13", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index 796c62efa3..e00a0f4ac0 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index da6baa3e67..c7f70e587b 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.87 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.86 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 01ac435b6b..5c90b7e0a3 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.86", + "version": "0.1.87", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 7a4bebe4eb..3c156d70e5 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.120 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.0.119 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index f35759d05d..a70789c358 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.119", + "version": "0.0.120", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad174b46b5..38387194dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.68 + specifier: ^0.1.69 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.12 + specifier: ^0.1.13 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.6 + specifier: ^0.3.7 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.6 + specifier: ^0.3.7 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.6 + specifier: ^0.3.7 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.22 + specifier: ^0.2.23 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 91c24c6df4579bcca320227a9da4e222ccc42215 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 23 Jun 2026 13:49:32 +0200 Subject: [PATCH 06/58] Docs. --- docs/collections/powersync-collection.md | 167 ++++++++++++++++++ .../src/attachments.ts | 4 + 2 files changed, 171 insertions(+) diff --git a/docs/collections/powersync-collection.md b/docs/collections/powersync-collection.md index c8ddbabbbe..b484c5bb92 100644 --- a/docs/collections/powersync-collection.md +++ b/docs/collections/powersync-collection.md @@ -1099,4 +1099,171 @@ const liveQuery = createLiveQueryCollection({ completed: todo.completed, })), }) +``` + +## Attachments + +`@tanstack/powersync-db-collection` ships `TanStackDBAttachmentQueue`, an [`AttachmentQueue`](https://docs.powersync.com/usage/use-case-examples/attachments-files) whose file operations commit inside a TanStack DB collection transaction. This lets you create (or delete) an attachment and mutate a related collection row (for example, setting `lists.photo_id`) atomically in a single transaction, instead of issuing two independent writes. + +The queue extends PowerSync's `AttachmentQueue`, so the generic concepts are unchanged and documented once in the SDK. + +> This section only covers what is specific to the TanStack DB integration. For storage adapters (local and remote), the `AttachmentTable` schema primitive, error-handling/retry semantics, and the `startSync()` / `stopSync()` lifecycle, see the [PowerSync attachments documentation](https://docs.powersync.com/usage/use-case-examples/attachments-files). + +### Prerequisites + +These are standard PowerSync attachment requirements. See the SDK attachments docs for details. + +- An `AttachmentTable` in your schema: + + ```ts + import { AttachmentTable, Schema } from "@powersync/web" + + const APP_SCHEMA = new Schema({ + // ...your tables + attachments: new AttachmentTable(), + }) + ``` + +- A local storage adapter (such as `IndexDBFileSystemStorageAdapter` on web) and a remote storage adapter (an implementation of the SDK's `RemoteStorageAdapter`, for example backed by Supabase Storage). Both are generic to all attachment users. See the SDK docs for the available adapters and the remote-adapter contract. + +### 1. Create the attachments collection + +This is the piece that makes the integration TanStack-aware: a normal PowerSync collection over the attachments table. The queue reads and writes attachment records through it. + +```ts +import { createCollection } from "@tanstack/react-db" +import { powerSyncCollectionOptions } from "@tanstack/powersync-db-collection" + +const attachmentsCollection = createCollection( + powerSyncCollectionOptions({ + database: db, + table: APP_SCHEMA.props.attachments, + }) +) +``` + +### 2. Construct the queue + +Pass your collection as `attachmentsCollection` alongside the standard `AttachmentQueue` options. Only `attachmentsCollection` and `watchAttachments` (below) are specific to this package; `db`, `localStorage`, `remoteStorage`, and `errorHandler` are the usual SDK options. + +```ts +import { TanStackDBAttachmentQueue } from "@tanstack/powersync-db-collection" + +const attachmentQueue = new TanStackDBAttachmentQueue({ + db, + attachmentsCollection, // TanStack DB collection over your AttachmentTable + localStorage, // SDK local storage adapter + remoteStorage, // your RemoteStorageAdapter (see SDK docs) + watchAttachments, // see step 3 + errorHandler, // standard AttachmentQueue error handler (see SDK docs) +}) +``` + +Start and stop syncing with the standard `attachmentQueue.startSync()` / `attachmentQueue.stopSync()` lifecycle (see SDK docs), typically inside a React effect or provider. + +### 3. Tell the queue which attachments exist (`watchAttachments`) + +`watchAttachments` reports the set of attachment IDs your data currently references, so the queue knows what to download and what to archive. With TanStack DB you drive it from a live query: emit the initial state, then re-emit the complete set on every change, and clean up on abort. + +```ts +import { + createCollection, + isNull, + liveQueryCollectionOptions, + not, +} from "@tanstack/db" +import { WatchedAttachmentItem } from "@powersync/web" + +const watchAttachments = async (onUpdate, abortSignal) => { + // Every row in your data model that references an attachment. + const livePhotoIds = createCollection( + liveQueryCollectionOptions({ + query: (q) => + q + .from({ document: listsCollection }) + .where(({ document }) => not(isNull(document.photo_id))) + .select(({ document }) => ({ photo_id: document.photo_id })), + }) + ) + + const mapper = (item) => + ({ + id: item.photo_id, + fileExtension: "jpg", + }) satisfies WatchedAttachmentItem + + // 1. Report the initial set of referenced attachment IDs. + const initialState = await livePhotoIds.stateWhenReady() + onUpdate(Array.from(initialState.values()).map(mapper)) + + // 2. Re-emit the whole set on every change (the queue expects the holistic state). + livePhotoIds.subscribeChanges(() => { + onUpdate(livePhotoIds.map(mapper)) + }) + + // 3. Clean up when sync stops. + abortSignal.addEventListener("abort", () => livePhotoIds.cleanup(), { + once: true, + }) +} +``` + +> A `watchAttachmentsFromQuery(...)` convenience helper that collapses this boilerplate into a single call is planned. Until then, use the pattern above. + +### 4. Save an attachment atomically with related data + +`saveFileTanStack` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all. + +```ts +await attachmentQueue.saveFileTanStack({ + data, // file bytes (ArrayBuffer / base64, per your local adapter) + fileExtension: "jpg", + updateHook: async (attachmentRecord) => { + // Runs in the same transaction as the attachment insert. + listsCollection.insert({ + id: crypto.randomUUID(), + name, + created_at: new Date(), + owner_id: userID, + photo_id: attachmentRecord.id, // associate the row with the attachment + }) + }, +}) +``` + +### 5. Delete an attachment and detach it from the row + +`deleteFileTanStack` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent. + +```ts +await attachmentQueue.deleteFileTanStack({ + id: photo_id, + updateHook: async () => { + listsCollection.update(listId, (draft) => { + draft.photo_id = null + }) + }, +}) +``` + +### 6. Display attachments via a live-query join + +Join your attachments collection into a live query to read the local URI (the locally cached file path) alongside your domain rows: + +```ts +import { eq } from "@tanstack/db" + +const { data } = useLiveQuery((q) => + q + .from({ lists: listsCollection }) + .leftJoin({ attachment: attachmentsCollection }, ({ lists, attachment }) => + eq(lists.photo_id, attachment.id) + ) + .select(({ lists, attachment }) => ({ + id: lists.id, + name: lists.name, + photo_id: lists.photo_id, + attachment_local_uri: attachment?.local_uri, + })) +) ``` \ No newline at end of file diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index c3c9d8f677..cbda937226 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -40,6 +40,10 @@ export interface SaveFileTanStackOptions { export interface DeleteFileTanStackOptions { id: string + /** * + * Note that this is called inside a synchronous TanStackDB transaction, + * any mutations made to other collections will be in the same transaction. + */ updateHook?: (attachment: AttachmentQueueRow) => Promise } From 3770d3b3e87966ec55775cae46e0ea762a136a4c Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 23 Jun 2026 14:01:10 +0200 Subject: [PATCH 07/58] changeset. --- .changeset/curly-planets-lead.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/curly-planets-lead.md diff --git a/.changeset/curly-planets-lead.md b/.changeset/curly-planets-lead.md new file mode 100644 index 0000000000..891ffbb522 --- /dev/null +++ b/.changeset/curly-planets-lead.md @@ -0,0 +1,6 @@ +--- +'@tanstack/powersync-db-collection': patch +--- + +Add attachments support via `TanStackDBAttachmentQueue`. This extends the PowerSync SDK's `AttachmentQueue` and backs it with +a TanStack DB collection, so file uploads/deletes are managed atomically alongside the relational data. From 307fdf80f522a39a50e316316b3b75ba27fd5e84 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 23 Jun 2026 18:58:41 +0200 Subject: [PATCH 08/58] fix: preload hangs forever after cleanup (#1576) (#1606) * test: cover re-preloading a live query after cleanup Add a regression test asserting that a live query loads its data again when it is preloaded after the live query and its source collection were cleaned up (e.g. when switching data sets at runtime). Co-Authored-By: Claude Opus 4.8 (1M context) * fix: reset live query error state on sync restart so preload recovers after cleanup (#1576) A source collection that is cleaned up while a live query depends on it pushes the live query into an error state and latches `isInErrorState`. That flag was never reset, so when sync restarted via preload() after cleanup (e.g. switching profiles without a page refresh), updateLiveQueryStatus() returned early, markReady() was never called and the preload promise hung forever. Reset `isInErrorState` at the start of each sync session so the live query can become ready again. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .changeset/preload-after-cleanup-1576.md | 7 +++++ .../query/live/collection-config-builder.ts | 2 ++ .../tests/query/live-query-collection.test.ts | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 .changeset/preload-after-cleanup-1576.md diff --git a/.changeset/preload-after-cleanup-1576.md b/.changeset/preload-after-cleanup-1576.md new file mode 100644 index 0000000000..132aeaf929 --- /dev/null +++ b/.changeset/preload-after-cleanup-1576.md @@ -0,0 +1,7 @@ +--- +'@tanstack/db': patch +--- + +Fix live query `preload()` hanging forever after a source collection was cleaned up (#1576) + +When a source collection is cleaned up while a live query depends on it, the live query transitions to an error state and latches an internal `isInErrorState` flag. That flag was never reset, so restarting sync (e.g. calling `preload()` again after cleanup when switching profiles) left the live query unable to become ready and the returned promise never resolved. The flag is now cleared at the start of each sync session so the live query can recover. diff --git a/packages/db/src/query/live/collection-config-builder.ts b/packages/db/src/query/live/collection-config-builder.ts index b40e6e431a..8faa55265f 100644 --- a/packages/db/src/query/live/collection-config-builder.ts +++ b/packages/db/src/query/live/collection-config-builder.ts @@ -596,6 +596,8 @@ export class CollectionConfigBuilder< private syncFn(config: SyncMethods) { // Store reference to the live query collection for error state transitions this.liveQueryCollection = config.collection + // Reset error state from any previous sync session so a restarted sync can become ready again. + this.isInErrorState = false // Store config and syncState as instance properties for the duration of this sync session this.currentSyncConfig = config diff --git a/packages/db/tests/query/live-query-collection.test.ts b/packages/db/tests/query/live-query-collection.test.ts index 8b2b609016..29d10cc27d 100644 --- a/packages/db/tests/query/live-query-collection.test.ts +++ b/packages/db/tests/query/live-query-collection.test.ts @@ -594,6 +594,33 @@ describe(`createLiveQueryCollection`, () => { finalSubscription.unsubscribe() }) + it(`loads its data again when preloaded after the live query and its source collection were cleaned up`, async () => { + const activeUsers = createLiveQueryCollection({ + query: (q) => + q + .from({ user: usersCollection }) + .where(({ user }) => eq(user.active, true)), + }) + + await activeUsers.preload() + expect(activeUsers.status).toBe(`ready`) + expect(activeUsers.size).toBe(2) + + // Tear down the source collection and the live query, e.g. when switching + // to a different data set at runtime. Cleaning up a source collection puts + // the dependent live query into an error state. + await usersCollection.cleanup() + expect(activeUsers.status).toBe(`error`) + + await activeUsers.cleanup() + expect(activeUsers.status).toBe(`cleaned-up`) + + // Preloading again restarts sync and resolves once the data is loaded. + await activeUsers.preload() + expect(activeUsers.status).toBe(`ready`) + expect(activeUsers.size).toBe(2) + }) + it(`should handle temporal values correctly in live queries`, async () => { // Define a type with temporal values type Task = { From 45617c45b761ee32d6cacc1a4ee9e653a27680dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:07:45 -0600 Subject: [PATCH 09/58] ci: Version Packages (#1612) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/preload-after-cleanup-1576.md | 7 -- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 7 ++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 8 +++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 59 files changed, 264 insertions(+), 99 deletions(-) delete mode 100644 .changeset/preload-after-cleanup-1576.md diff --git a/.changeset/preload-after-cleanup-1576.md b/.changeset/preload-after-cleanup-1576.md deleted file mode 100644 index 132aeaf929..0000000000 --- a/.changeset/preload-after-cleanup-1576.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Fix live query `preload()` hanging forever after a source collection was cleaned up (#1576) - -When a source collection is cleaned up while a live query depends on it, the live query transitions to an error state and latches an internal `isInErrorState` flag. That flag was never reset, so restarting sync (e.g. calling `preload()` again after cleanup when switching profiles) left the live query unable to become ready and the returned promise never resolved. The flag is now cleared at the start of each sync session so the live query can recover. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index b4c64bfacc..7829800350 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.69", - "@tanstack/db": "^0.6.9", + "@tanstack/angular-db": "^0.1.70", + "@tanstack/db": "^0.6.10", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index ebe72a04c5..2ba2aea097 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.13", - "@tanstack/node-db-sqlite-persistence": "^0.2.1", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/electron-db-sqlite-persistence": "^0.1.14", + "@tanstack/node-db-sqlite-persistence": "^0.2.2", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 82b07e3c31..a8ec7a0635 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.9", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", + "@tanstack/db": "^0.6.10", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.2", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 8db8659ca3..8b4fee8599 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.9", - "@tanstack/electric-db-collection": "^0.3.7", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/react-db": "^0.1.87", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", + "@tanstack/db": "^0.6.10", + "@tanstack/electric-db-collection": "^0.3.8", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/react-db": "^0.1.88", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.2", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index eb798b2b86..212d0c0a7a 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.1", - "@tanstack/db": "^0.6.9", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/browser-db-sqlite-persistence": "^0.2.2", + "@tanstack/db": "^0.6.10", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 290ed30adc..c9ea092585 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.9", - "@tanstack/react-db": "^0.1.87", + "@tanstack/db": "^0.6.10", + "@tanstack/react-db": "^0.1.88", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 2f68958d4d..837e2f371b 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index 9145e3ff5f..dc7365451e 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.7", + "@tanstack/electric-db-collection": "^0.3.8", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.87", + "@tanstack/trailbase-db-collection": "^0.1.88", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index efdec9ece8..e19558c2e6 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.7", + "@tanstack/electric-db-collection": "^0.3.8", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/solid-db": "^0.2.23", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/solid-db": "^0.2.24", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.87", + "@tanstack/trailbase-db-collection": "^0.1.88", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index cc5e8b4e34..09961d8f45 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.70 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.69 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 3e7a47e0c9..509fd65f04 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.69", + "version": "0.1.70", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index 5dcf408756..a7963d8ac7 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 30065efe96..b57349c6c8 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 2c238a9a83..2c19dc8950 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index fa47e3e79f..f9809c5dec 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + - @tanstack/capacitor-db-sqlite-persistence@0.2.2 + ## 0.0.13 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 90872c4797..f180809b89 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.13", + "version": "0.0.14", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 2ccf6f8fbc..171b52bf5b 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index f0b263dcf8..3aacab134c 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 4394bb569a..500030ec02 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index 68b5463537..3a1c664490 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.2 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.2.1 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 076abc5a81..26cdd105c4 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.1", + "version": "0.2.2", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index 2c8ff69a7f..a6574da80f 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/db +## 0.6.10 + +### Patch Changes + +- Fix live query `preload()` hanging forever after a source collection was cleaned up (#1576) ([#1606](https://github.com/TanStack/db/pull/1606)) + + When a source collection is cleaned up while a live query depends on it, the live query transitions to an error state and latches an internal `isInErrorState` flag. That flag was never reset, so restarting sync (e.g. calling `preload()` again after cleanup when switching profiles) left the live query unable to become ready and the returned promise never resolved. The flag is now cleared at the start of each sync session so the live query can recover. + ## 0.6.9 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 7939c67c55..03d84b67ea 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.9", + "version": "0.6.10", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 3c670daf9d..7d02c6ec29 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.8 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.3.7 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index db931807d7..9c03109450 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.7", + "version": "0.3.8", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 73de7a2e6d..7bac5e1b85 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.14 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.1.13 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index b342835f7b..80d7b57dd5 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.13", + "version": "0.1.14", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index fcab9b256e..68fa4658c9 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index 4750ae2ba7..80f5d686f6 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + - @tanstack/expo-db-sqlite-persistence@0.2.2 + ## 0.0.13 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 01e45379d0..fb7d4c2fe2 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.13", + "version": "0.0.14", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 54eab71556..9e71f67794 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index fbae0befa1..f685d49021 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index 386f79f795..ce5eefc2e9 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index 4973473334..dbb4255300 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.35 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 1.0.34 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index 1020be51bc..c336d46380 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.34", + "version": "1.0.35", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index b24bb05e72..a5c28761ad 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.48 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.47 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index a13faca7f0..39466dc011 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.47", + "version": "0.1.48", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index 58727ae34b..3e868617c0 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.42 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 1.0.41 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index 9a0b654121..fc58d3dbc8 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.41", + "version": "1.0.42", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index e12c46b786..2322d676c9 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.88 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.87 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 2ac29a8fa4..7b7ac675c3 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.87", + "version": "0.1.88", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index bd6a90668c..7029e671b8 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index b59c3b5b49..f42bfa63ce 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index dcd6edd405..21cac83f26 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.76 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.75 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index 06a259ecfb..98530d438c 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.75", + "version": "0.1.76", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 63d736f91c..a9ef5cc2f4 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.24 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.2.23 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index d5d65a6bfc..dbaa187d57 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.23", + "version": "0.2.24", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 388208fbc7..45333b0511 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.87 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.86 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index e7da54fe70..c3c7b292ae 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.86", + "version": "0.1.87", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 47726fa7ad..97893f5f26 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 2279f773b7..287bbd79a3 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + - @tanstack/tauri-db-sqlite-persistence@0.2.2 + ## 0.0.13 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 6d568d4c41..a5c17377ed 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.13", + "version": "0.0.14", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index e00a0f4ac0..1be0c86c24 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index c7f70e587b..f631d325cd 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.88 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.87 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 5c90b7e0a3..7403bb4587 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.87", + "version": "0.1.88", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 3c156d70e5..2e559852d4 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.121 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.0.120 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index a70789c358..fbec37e9ab 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.120", + "version": "0.0.121", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38387194dd..525d66dab4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.69 + specifier: ^0.1.70 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.13 + specifier: ^0.1.14 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.7 + specifier: ^0.3.8 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.7 + specifier: ^0.3.8 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.7 + specifier: ^0.3.8 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.23 + specifier: ^0.2.24 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 38af8c59b7766ebb1b9f53941c9916dd503508ea Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Wed, 24 Jun 2026 10:45:59 +0200 Subject: [PATCH 10/58] Rename saveFileTanStack => save, deleteFIleTanStack => delete. --- docs/collections/powersync-collection.md | 8 ++++---- .../powersync-db-collection/src/attachments.ts | 13 +++++-------- .../tests/attachments.test.ts | 18 +++++++++--------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/docs/collections/powersync-collection.md b/docs/collections/powersync-collection.md index b484c5bb92..8799f96aa8 100644 --- a/docs/collections/powersync-collection.md +++ b/docs/collections/powersync-collection.md @@ -1212,10 +1212,10 @@ const watchAttachments = async (onUpdate, abortSignal) => { ### 4. Save an attachment atomically with related data -`saveFileTanStack` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all. +`save` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all. ```ts -await attachmentQueue.saveFileTanStack({ +await attachmentQueue.save({ data, // file bytes (ArrayBuffer / base64, per your local adapter) fileExtension: "jpg", updateHook: async (attachmentRecord) => { @@ -1233,10 +1233,10 @@ await attachmentQueue.saveFileTanStack({ ### 5. Delete an attachment and detach it from the row -`deleteFileTanStack` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent. +`delete` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent. ```ts -await attachmentQueue.deleteFileTanStack({ +await attachmentQueue.delete({ id: photo_id, updateHook: async () => { listsCollection.update(listId, (draft) => { diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index cbda937226..f70f6494ec 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -25,7 +25,7 @@ export type TanStackDBAttachmentQueueOptions = AttachmentQueueOptions & { attachmentsCollection: Collection } -export interface SaveFileTanStackOptions { +export interface SaveOptions { data: AttachmentData fileExtension: string mediaType?: string @@ -38,7 +38,7 @@ export interface SaveFileTanStackOptions { updateHook?: (attachment: AttachmentQueueRow) => Promise } -export interface DeleteFileTanStackOptions { +export interface DeleteOptions { id: string /** * * Note that this is called inside a synchronous TanStackDB transaction, @@ -70,14 +70,14 @@ export class TanStackDBAttachmentQueue extends AttachmentQueue { * Exposes an `updateHook` option which is called inside a TanStackDB transaction, * relational associations with the provided attachment ID should be made in this hook. */ - async saveFileTanStack({ + async save({ data, fileExtension, mediaType, metaData, id, updateHook, - }: SaveFileTanStackOptions): Promise { + }: SaveOptions): Promise { const resolvedId = id ?? (await this.generateAttachmentId()) const filename = `${resolvedId}.${fileExtension}` const localUri = this.localStorage.getLocalUri(filename) @@ -128,10 +128,7 @@ export class TanStackDBAttachmentQueue extends AttachmentQueue { * Exposes an `updateHook` option which is called inside a TanStackDB transaction, * relational associations with the provided attachment ID should be cleaned up in this hook. */ - async deleteFileTanStack({ - id, - updateHook, - }: DeleteFileTanStackOptions): Promise { + async delete({ id, updateHook }: DeleteOptions): Promise { await this.withAttachmentContext(async (ctx) => { const tanStackDBTransaction = createTransaction({ autoCommit: false, diff --git a/packages/powersync-db-collection/tests/attachments.test.ts b/packages/powersync-db-collection/tests/attachments.test.ts index 411c7cd753..e13ea3dbf9 100644 --- a/packages/powersync-db-collection/tests/attachments.test.ts +++ b/packages/powersync-db-collection/tests/attachments.test.ts @@ -210,13 +210,13 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { ) } - describe(`saveFileTanStack`, () => { + describe(`save`, () => { it(`writes the local file and inserts a QUEUED_UPLOAD row into the collection`, async () => { const { createQueue, attachmentsCollection, localStorage } = await setup() const queue = createQueue() const data = new Uint8Array(123).fill(42).buffer - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data, fileExtension: `jpg`, mediaType: `image/jpeg`, @@ -245,7 +245,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { const queue = createQueue() const userId = randomUUID() - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data: createMockJpegBuffer(), fileExtension: `jpg`, updateHook: async (attachment) => { @@ -284,7 +284,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { await queue.startSync() const userId = randomUUID() - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data: createMockJpegBuffer(), fileExtension: `jpg`, updateHook: async (attachment) => { @@ -313,7 +313,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { const queue = createQueue() const id = `my-custom-id` - const record = await queue.saveFileTanStack({ + const record = await queue.save({ id, data: createMockJpegBuffer(), fileExtension: `png`, @@ -324,7 +324,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { }) }) - describe(`deleteFileTanStack`, () => { + describe(`delete file`, () => { it(`queues an existing attachment for deletion and removes the local file`, async () => { const { createQueue, @@ -336,7 +336,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { await queue.startSync() const userId = randomUUID() - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data: createMockJpegBuffer(), fileExtension: `jpg`, updateHook: async (attachment) => { @@ -355,7 +355,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { AttachmentState.SYNCED, ) - await queue.deleteFileTanStack({ + await queue.delete({ id: record.id, updateHook: async (attachment) => { usersCollection.update(userId, (draft) => { @@ -389,7 +389,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { const hook = vi.fn() await expect( - queue.deleteFileTanStack({ id: `does-not-exist`, updateHook: hook }), + queue.delete({ id: `does-not-exist`, updateHook: hook }), ).rejects.toThrow(/not found/i) // The failing transaction must not have run the hook or touched state. From d82ffc2b031e989143f0dde3ad630a0c7ab579a4 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Wed, 24 Jun 2026 16:04:29 +0200 Subject: [PATCH 11/58] Made `updateHook` synchronous and updated `AttachmentQueueRow` typing. --- packages/powersync-db-collection/package.json | 6 ++-- .../src/attachments.ts | 28 +++++++--------- .../tests/attachments.test.ts | 4 +-- pnpm-lock.yaml | 33 +++++++------------ 4 files changed, 29 insertions(+), 42 deletions(-) diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 9374371824..fde2ae3828 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -59,11 +59,11 @@ "p-defer": "^4.0.1" }, "peerDependencies": { - "@powersync/common": "^1.55.0" + "@powersync/common": "^1.57.0" }, "devDependencies": { - "@powersync/common": "1.55.0", - "@powersync/node": "0.18.1", + "@powersync/common": "1.57.0", + "@powersync/node": "0.19.2", "@types/debug": "^4.1.12", "@vitest/coverage-istanbul": "^3.2.4", "better-sqlite3": "^12.6.2" diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index f70f6494ec..d3c48fb1d8 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -1,8 +1,6 @@ import { AttachmentQueue, - AttachmentState, - AttachmentTable, - Schema, + AttachmentState } from '@powersync/common' import { createTransaction } from '@tanstack/db' import { PowerSyncTransactor } from './PowerSyncTransactor' @@ -11,10 +9,10 @@ import type { AbstractPowerSyncDatabase, AttachmentData, AttachmentQueueOptions, -} from '@powersync/common' -import type { Collection } from '@tanstack/db' -export type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] + AttachmentTable} from '@powersync/common' +import type { Collection } from '@tanstack/db' +import type { OptionalExtractedTable } from './helpers' export type TanStackDBAttachmentQueueOptions = AttachmentQueueOptions & { /** @@ -32,24 +30,22 @@ export interface SaveOptions { metaData?: string id?: string /** - * Note that this is called inside a synchronous TanStackDB transaction, - * any mutations made to other collections will be in the same transaction. + * Called within the same TanStackDB transaction as the attachment write, + * so any mutations made to other collections are committed atomically with it. */ - updateHook?: (attachment: AttachmentQueueRow) => Promise + updateHook?: (attachment: AttachmentQueueRow) => void } export interface DeleteOptions { id: string - /** * - * Note that this is called inside a synchronous TanStackDB transaction, - * any mutations made to other collections will be in the same transaction. + /** + * Called within the same TanStackDB transaction as the attachment write, + * so any mutations made to other collections are committed atomically with it. */ - updateHook?: (attachment: AttachmentQueueRow) => Promise + updateHook?: (attachment: AttachmentQueueRow) => void } -const _tmpSchema = new Schema({ - attachments: new AttachmentTable(), -}) +export type AttachmentQueueRow = OptionalExtractedTable /** * A custom extension of the PowerSyncAttachmentQueue for TanStackDB. diff --git a/packages/powersync-db-collection/tests/attachments.test.ts b/packages/powersync-db-collection/tests/attachments.test.ts index e13ea3dbf9..c5925e234f 100644 --- a/packages/powersync-db-collection/tests/attachments.test.ts +++ b/packages/powersync-db-collection/tests/attachments.test.ts @@ -229,7 +229,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { expect(record.has_synced).toBe(0) // The file should exist on disk at the returned local_uri. - expect(await localStorage.fileExists(record.local_uri)).toBe(true) + expect(await localStorage.fileExists(record.local_uri!)).toBe(true) // The row should be reflected in the collection once it syncs back. await waitForState( @@ -379,7 +379,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { () => expect(attachmentsCollection.get(record.id)).toBeUndefined(), { timeout: WAIT_TIMEOUT, interval: 50 }, ) - expect(await localStorage.fileExists(record.local_uri)).toBe(false) + expect(await localStorage.fileExists(record.local_uri!)).toBe(false) }) it(`throws for an unknown id and commits nothing`, async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3852ece3ed..6e61ea7bdd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,11 +1338,11 @@ importers: version: 4.0.1 devDependencies: '@powersync/common': - specifier: 1.55.0 - version: 1.55.0 + specifier: 1.57.0 + version: 1.57.0 '@powersync/node': - specifier: 0.18.1 - version: 0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0) + specifier: 0.19.2 + version: 0.19.2(@powersync/common@1.57.0)(better-sqlite3@12.8.0) '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -4831,13 +4831,13 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@powersync/common@1.55.0': - resolution: {integrity: sha512-c9K2Gac9wOB4ijVnQT388g7Yeuh6VrfJZFXaL6Rag9Fp7F8JzAYcrWaULLTdMnzm7VW6b6XT5M2ip5yLS8oSBg==} + '@powersync/common@1.57.0': + resolution: {integrity: sha512-uYccCxK5mwahELRouY3YY584TZgjFU8wPPKZQQ6sAOUoMikV8D/+v+UYsNI280MKMnhFqLkxk4TPZIG7ArIzTQ==} - '@powersync/node@0.18.1': - resolution: {integrity: sha512-fcTICgs61CAEb39xiC7pedYsPgbjUInJ/47dr7RIdnEHpAgjWH8bW95/b70qK1fQUANy9lKBBF3PcmfswVgfCw==} + '@powersync/node@0.19.2': + resolution: {integrity: sha512-lF7v/rkiLujAojn7Vjgvs1AibhL5zlEQVYO0iCUGoE1S1Hw7lxfUvAa1mTneKWCEmj0EC9yQBHkPUyBDZXVdLA==} peerDependencies: - '@powersync/common': ^1.49.0 + '@powersync/common': ^1.57.0 better-sqlite3: 12.x peerDependenciesMeta: better-sqlite3: @@ -6799,9 +6799,6 @@ packages: async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} - async-mutex@0.5.0: - resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -16480,15 +16477,13 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@powersync/common@1.55.0': + '@powersync/common@1.57.0': dependencies: js-logger: 1.6.1 - '@powersync/node@0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0)': + '@powersync/node@0.19.2(@powersync/common@1.57.0)(better-sqlite3@12.8.0)': dependencies: - '@powersync/common': 1.55.0 - async-mutex: 0.5.0 - bson: 6.10.4 + '@powersync/common': 1.57.0 comlink: 4.4.2 undici: 7.24.4 optionalDependencies: @@ -18956,10 +18951,6 @@ snapshots: async-limiter@1.0.1: {} - async-mutex@0.5.0: - dependencies: - tslib: 2.8.1 - asynckit@0.4.0: {} at-least-node@1.0.0: {} From 3e59ca5b28fc00cdd2acd102041c94ce5cc34383 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:50:46 -0600 Subject: [PATCH 12/58] docs: regenerate API documentation (#1477) Co-authored-by: github-actions[bot] --- .../namespaces/IR/classes/Aggregate.md | 12 +- .../namespaces/IR/classes/CollectionRef.md | 12 +- .../IR/classes/ConditionalSelect.md | 98 ++ .../@tanstack/namespaces/IR/classes/Func.md | 12 +- .../namespaces/IR/classes/IncludesSubquery.md | 24 +- .../namespaces/IR/classes/PropRef.md | 10 +- .../namespaces/IR/classes/QueryRef.md | 12 +- .../namespaces/IR/classes/UnionAll.md | 105 ++ .../namespaces/IR/classes/UnionFrom.md | 100 ++ .../@tanstack/namespaces/IR/classes/Value.md | 10 +- .../IR/functions/createResidualWhere.md | 2 +- .../namespaces/IR/functions/followRef.md | 2 +- .../IR/functions/getHavingExpression.md | 2 +- .../IR/functions/getWhereExpression.md | 2 +- .../IR/functions/isExpressionLike.md | 2 +- .../IR/functions/isResidualWhere.md | 2 +- .../@tanstack/namespaces/IR/index.md | 5 + .../namespaces/IR/interfaces/JoinClause.md | 10 +- .../IR/type-aliases/BasicExpression.md | 2 +- .../type-aliases/ConditionalSelectBranch.md | 32 + .../namespaces/IR/type-aliases/From.md | 6 +- .../namespaces/IR/type-aliases/GroupBy.md | 2 +- .../namespaces/IR/type-aliases/Having.md | 2 +- .../type-aliases/IncludesMaterialization.md | 2 +- .../namespaces/IR/type-aliases/Join.md | 2 +- .../namespaces/IR/type-aliases/Limit.md | 2 +- .../namespaces/IR/type-aliases/Offset.md | 2 +- .../namespaces/IR/type-aliases/OrderBy.md | 2 +- .../IR/type-aliases/OrderByClause.md | 6 +- .../IR/type-aliases/OrderByDirection.md | 2 +- .../namespaces/IR/type-aliases/Select.md | 3 +- .../IR/type-aliases/SelectValueExpression.md | 17 + .../namespaces/IR/type-aliases/Where.md | 2 +- .../IR/variables/INCLUDES_SCALAR_FIELD.md | 2 +- .../AggregateFunctionNotInSelectError.md | 4 +- .../classes/AggregateNotSupportedError.md | 4 +- docs/reference/classes/BaseQueryBuilder.md | 138 +- .../CannotCombineEmptyExpressionListError.md | 4 +- docs/reference/classes/CollectionImpl.md | 98 +- .../classes/CollectionInputNotFoundError.md | 4 +- .../classes/DistinctRequiresSelectError.md | 4 +- .../classes/DuplicateAliasInSubqueryError.md | 4 +- .../classes/EmptyReferencePathError.md | 4 +- .../classes/FnSelectWithGroupByError.md | 4 +- docs/reference/classes/GroupByError.md | 4 +- .../classes/HavingRequiresGroupByError.md | 4 +- .../reference/classes/InvalidJoinCondition.md | 4 +- .../InvalidJoinConditionLeftSourceError.md | 4 +- .../InvalidJoinConditionRightSourceError.md | 4 +- .../InvalidJoinConditionSameSourceError.md | 4 +- ...InvalidJoinConditionSourceMismatchError.md | 4 +- .../classes/InvalidSourceTypeError.md | 6 +- .../classes/InvalidStorageDataFormatError.md | 4 +- .../InvalidStorageObjectFormatError.md | 4 +- .../classes/InvalidWhereExpressionError.md | 4 +- .../classes/JoinCollectionNotFoundError.md | 4 +- .../JoinConditionMustBeEqualityError.md | 4 +- docs/reference/classes/JoinError.md | 4 +- .../classes/LimitOffsetRequireOrderByError.md | 4 +- .../classes/LocalStorageCollectionError.md | 4 +- .../classes/MissingAliasInputsError.md | 4 +- ...NonAggregateExpressionNotInGroupByError.md | 4 +- .../classes/QueryCompilationError.md | 4 +- .../classes/QueryMustHaveFromClauseError.md | 4 +- docs/reference/classes/QueryOptimizerError.md | 4 +- docs/reference/classes/SerializationError.md | 4 +- .../classes/SetWindowRequiresOrderByError.md | 4 +- docs/reference/classes/StorageError.md | 4 +- .../classes/StorageKeyRequiredError.md | 4 +- .../classes/SubscriptionNotFoundError.md | 4 +- docs/reference/classes/SyncCleanupError.md | 4 +- .../classes/UnknownExpressionTypeError.md | 4 +- .../reference/classes/UnknownFunctionError.md | 4 +- .../UnknownHavingExpressionTypeError.md | 4 +- .../UnsupportedAggregateFunctionError.md | 4 +- .../classes/UnsupportedFromTypeError.md | 4 +- .../classes/UnsupportedJoinSourceTypeError.md | 4 +- .../classes/UnsupportedJoinTypeError.md | 4 +- .../UnsupportedRootScalarSelectError.md | 4 +- .../classes/WhereClauseConversionError.md | 4 +- .../functions/isChangeMessage.md | 2 +- .../functions/isControlMessage.md | 2 +- docs/reference/functions/add.md | 6 +- docs/reference/functions/and.md | 4 +- docs/reference/functions/avg.md | 2 +- docs/reference/functions/caseWhen.md | 1299 +++++++++++++++++ docs/reference/functions/coalesce.md | 2 +- docs/reference/functions/compileQuery.md | 2 +- docs/reference/functions/concat.md | 4 +- docs/reference/functions/count.md | 2 +- docs/reference/functions/createCollection.md | 16 +- docs/reference/functions/createTransaction.md | 2 +- docs/reference/functions/divide.md | 36 + docs/reference/functions/eq.md | 6 +- .../functions/getActiveTransaction.md | 2 +- docs/reference/functions/gt.md | 6 +- docs/reference/functions/gte.md | 6 +- docs/reference/functions/ilike.md | 2 +- docs/reference/functions/inArray.md | 2 +- docs/reference/functions/isNull.md | 2 +- docs/reference/functions/isUndefined.md | 2 +- docs/reference/functions/length.md | 2 +- docs/reference/functions/like.md | 2 +- .../functions/localOnlyCollectionOptions.md | 4 +- .../localStorageCollectionOptions.md | 4 +- docs/reference/functions/lower.md | 2 +- docs/reference/functions/lt.md | 6 +- docs/reference/functions/lte.md | 6 +- docs/reference/functions/materialize.md | 58 + docs/reference/functions/max.md | 2 +- docs/reference/functions/min.md | 2 +- docs/reference/functions/multiply.md | 36 + docs/reference/functions/not.md | 2 +- docs/reference/functions/or.md | 4 +- docs/reference/functions/safeRandomUUID.md | 25 + docs/reference/functions/subtract.md | 36 + docs/reference/functions/sum.md | 2 +- docs/reference/functions/toArray.md | 2 +- docs/reference/functions/upper.md | 2 +- docs/reference/index.md | 10 + docs/reference/interfaces/Collection.md | 98 +- docs/reference/interfaces/CollectionLike.md | 12 +- docs/reference/interfaces/Context.md | 51 +- .../interfaces/LocalOnlyCollectionConfig.md | 4 +- .../interfaces/LocalOnlyCollectionUtils.md | 4 +- .../LocalStorageCollectionConfig.md | 10 +- .../interfaces/LocalStorageCollectionUtils.md | 8 +- .../reference/interfaces/ParseWhereOptions.md | 64 + docs/reference/interfaces/Parser.md | 6 +- docs/reference/interfaces/Transaction.md | 50 +- .../functions/queryCollectionOptions.md | 8 +- .../interfaces/QueryCollectionConfig.md | 23 +- .../interfaces/QueryCollectionUtils.md | 32 +- .../ApplyJoinOptionalityToMergedSchema.md | 10 +- docs/reference/type-aliases/ClearStorageFn.md | 2 +- .../type-aliases/ContextFromSource.md | 58 + .../type-aliases/ContextFromUnionBranches.md | 88 ++ .../type-aliases/ContextFromUnionSource.md | 18 + docs/reference/type-aliases/ContextSchema.md | 2 +- docs/reference/type-aliases/ExtractContext.md | 2 +- .../type-aliases/FunctionalHavingRow.md | 2 +- docs/reference/type-aliases/GetResult.md | 2 +- .../type-aliases/GetStorageSizeFn.md | 2 +- .../reference/type-aliases/GroupByCallback.md | 2 +- .../type-aliases/InferCollectionType.md | 2 +- .../reference/type-aliases/InferResultType.md | 2 +- .../type-aliases/InitialQueryBuilder.md | 4 +- docs/reference/type-aliases/JoinOnCallback.md | 2 +- .../type-aliases/LiveQueryCollectionUtils.md | 2 +- .../MergeContextForJoinCallback.md | 10 +- .../type-aliases/MergeContextWithJoinType.md | 12 +- docs/reference/type-aliases/OperatorName.md | 2 +- .../reference/type-aliases/OrderByCallback.md | 2 +- docs/reference/type-aliases/Prettify.md | 2 +- docs/reference/type-aliases/QueryBuilder.md | 4 +- docs/reference/type-aliases/QueryResult.md | 2 +- docs/reference/type-aliases/Ref.md | 2 +- docs/reference/type-aliases/RefsForContext.md | 19 +- .../type-aliases/ResultTypeFromSelect.md | 4 +- .../type-aliases/SchemaFromSource.md | 2 +- docs/reference/type-aliases/SelectObject.md | 2 +- docs/reference/type-aliases/SingleSource.md | 18 + docs/reference/type-aliases/Source.md | 6 +- .../type-aliases/SourceClauseContext.md | 12 + docs/reference/type-aliases/StorageApi.md | 2 +- .../reference/type-aliases/StorageEventApi.md | 6 +- docs/reference/type-aliases/WhereCallback.md | 2 +- docs/reference/type-aliases/WithResult.md | 2 +- docs/reference/variables/Query.md | 2 +- docs/reference/variables/operators.md | 4 +- 170 files changed, 2734 insertions(+), 489 deletions(-) create mode 100644 docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md create mode 100644 docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md create mode 100644 docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md create mode 100644 docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md create mode 100644 docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md create mode 100644 docs/reference/functions/caseWhen.md create mode 100644 docs/reference/functions/divide.md create mode 100644 docs/reference/functions/materialize.md create mode 100644 docs/reference/functions/multiply.md create mode 100644 docs/reference/functions/safeRandomUUID.md create mode 100644 docs/reference/functions/subtract.md create mode 100644 docs/reference/type-aliases/ContextFromSource.md create mode 100644 docs/reference/type-aliases/ContextFromUnionBranches.md create mode 100644 docs/reference/type-aliases/ContextFromUnionSource.md create mode 100644 docs/reference/type-aliases/SingleSource.md create mode 100644 docs/reference/type-aliases/SourceClauseContext.md diff --git a/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md b/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md index 19066baac0..9cbcc566c8 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md @@ -5,7 +5,7 @@ title: Aggregate # Class: Aggregate\ -Defined in: [packages/db/src/query/ir.ts:129](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L129) +Defined in: [packages/db/src/query/ir.ts:166](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L166) ## Extends @@ -25,7 +25,7 @@ Defined in: [packages/db/src/query/ir.ts:129](https://github.com/TanStack/db/blo new Aggregate(name, args): Aggregate; ``` -Defined in: [packages/db/src/query/ir.ts:131](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L131) +Defined in: [packages/db/src/query/ir.ts:168](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L168) #### Parameters @@ -55,7 +55,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) **`Internal`** @@ -75,7 +75,7 @@ BaseExpression.__returnType args: BasicExpression[]; ``` -Defined in: [packages/db/src/query/ir.ts:133](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L133) +Defined in: [packages/db/src/query/ir.ts:170](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L170) *** @@ -85,7 +85,7 @@ Defined in: [packages/db/src/query/ir.ts:133](https://github.com/TanStack/db/blo name: string; ``` -Defined in: [packages/db/src/query/ir.ts:132](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L132) +Defined in: [packages/db/src/query/ir.ts:169](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L169) *** @@ -95,7 +95,7 @@ Defined in: [packages/db/src/query/ir.ts:132](https://github.com/TanStack/db/blo type: "agg"; ``` -Defined in: [packages/db/src/query/ir.ts:130](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L130) +Defined in: [packages/db/src/query/ir.ts:167](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L167) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md b/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md index 416a1c83e7..99c1b5ee85 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md @@ -5,7 +5,7 @@ title: CollectionRef # Class: CollectionRef -Defined in: [packages/db/src/query/ir.ts:76](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L76) +Defined in: [packages/db/src/query/ir.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L85) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/query/ir.ts:76](https://github.com/TanStack/db/blob new CollectionRef(collection, alias): CollectionRef; ``` -Defined in: [packages/db/src/query/ir.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L78) +Defined in: [packages/db/src/query/ir.ts:87](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L87) #### Parameters @@ -49,7 +49,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) **`Internal`** @@ -69,7 +69,7 @@ BaseExpression.__returnType alias: string; ``` -Defined in: [packages/db/src/query/ir.ts:80](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L80) +Defined in: [packages/db/src/query/ir.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L89) *** @@ -79,7 +79,7 @@ Defined in: [packages/db/src/query/ir.ts:80](https://github.com/TanStack/db/blob collection: CollectionImpl; ``` -Defined in: [packages/db/src/query/ir.ts:79](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L79) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) *** @@ -89,7 +89,7 @@ Defined in: [packages/db/src/query/ir.ts:79](https://github.com/TanStack/db/blob type: "collectionRef"; ``` -Defined in: [packages/db/src/query/ir.ts:77](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L77) +Defined in: [packages/db/src/query/ir.ts:86](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L86) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md b/docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md new file mode 100644 index 0000000000..278abc6aac --- /dev/null +++ b/docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md @@ -0,0 +1,98 @@ +--- +id: ConditionalSelect +title: ConditionalSelect +--- + +# Class: ConditionalSelect + +Defined in: [packages/db/src/query/ir.ts:204](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L204) + +## Extends + +- `BaseExpression` + +## Constructors + +### Constructor + +```ts +new ConditionalSelect(branches, defaultValue?): ConditionalSelect; +``` + +Defined in: [packages/db/src/query/ir.ts:206](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L206) + +#### Parameters + +##### branches + +[`ConditionalSelectBranch`](../type-aliases/ConditionalSelectBranch.md)[] + +##### defaultValue? + +[`SelectValueExpression`](../type-aliases/SelectValueExpression.md) + +#### Returns + +`ConditionalSelect` + +#### Overrides + +```ts +BaseExpression.constructor +``` + +## Properties + +### \_\_returnType + +```ts +readonly __returnType: any; +``` + +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) + +**`Internal`** + +- Type brand for TypeScript inference + +#### Inherited from + +```ts +BaseExpression.__returnType +``` + +*** + +### branches + +```ts +branches: ConditionalSelectBranch[]; +``` + +Defined in: [packages/db/src/query/ir.ts:207](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L207) + +*** + +### defaultValue? + +```ts +optional defaultValue: SelectValueExpression; +``` + +Defined in: [packages/db/src/query/ir.ts:208](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L208) + +*** + +### type + +```ts +type: "conditionalSelect"; +``` + +Defined in: [packages/db/src/query/ir.ts:205](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L205) + +#### Overrides + +```ts +BaseExpression.type +``` diff --git a/docs/reference/@tanstack/namespaces/IR/classes/Func.md b/docs/reference/@tanstack/namespaces/IR/classes/Func.md index 4d6fb79848..f18f5015fb 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/Func.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/Func.md @@ -5,7 +5,7 @@ title: Func # Class: Func\ -Defined in: [packages/db/src/query/ir.ts:114](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L114) +Defined in: [packages/db/src/query/ir.ts:151](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L151) ## Extends @@ -25,7 +25,7 @@ Defined in: [packages/db/src/query/ir.ts:114](https://github.com/TanStack/db/blo new Func(name, args): Func; ``` -Defined in: [packages/db/src/query/ir.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L116) +Defined in: [packages/db/src/query/ir.ts:153](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L153) #### Parameters @@ -55,7 +55,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) **`Internal`** @@ -75,7 +75,7 @@ BaseExpression.__returnType args: BasicExpression[]; ``` -Defined in: [packages/db/src/query/ir.ts:118](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L118) +Defined in: [packages/db/src/query/ir.ts:155](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L155) *** @@ -85,7 +85,7 @@ Defined in: [packages/db/src/query/ir.ts:118](https://github.com/TanStack/db/blo name: string; ``` -Defined in: [packages/db/src/query/ir.ts:117](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L117) +Defined in: [packages/db/src/query/ir.ts:154](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L154) *** @@ -95,7 +95,7 @@ Defined in: [packages/db/src/query/ir.ts:117](https://github.com/TanStack/db/blo type: "func"; ``` -Defined in: [packages/db/src/query/ir.ts:115](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L115) +Defined in: [packages/db/src/query/ir.ts:152](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L152) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md b/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md index 8d932c9ead..4da4ae8a77 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md @@ -5,7 +5,7 @@ title: IncludesSubquery # Class: IncludesSubquery -Defined in: [packages/db/src/query/ir.ts:139](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L139) +Defined in: [packages/db/src/query/ir.ts:176](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L176) ## Extends @@ -27,7 +27,7 @@ new IncludesSubquery( scalarField?): IncludesSubquery; ``` -Defined in: [packages/db/src/query/ir.ts:141](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L141) +Defined in: [packages/db/src/query/ir.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L178) #### Parameters @@ -81,7 +81,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) **`Internal`** @@ -101,7 +101,7 @@ BaseExpression.__returnType childCorrelationField: PropRef; ``` -Defined in: [packages/db/src/query/ir.ts:144](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L144) +Defined in: [packages/db/src/query/ir.ts:181](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L181) *** @@ -111,7 +111,7 @@ Defined in: [packages/db/src/query/ir.ts:144](https://github.com/TanStack/db/blo correlationField: PropRef; ``` -Defined in: [packages/db/src/query/ir.ts:143](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L143) +Defined in: [packages/db/src/query/ir.ts:180](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L180) *** @@ -121,7 +121,7 @@ Defined in: [packages/db/src/query/ir.ts:143](https://github.com/TanStack/db/blo fieldName: string; ``` -Defined in: [packages/db/src/query/ir.ts:145](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L145) +Defined in: [packages/db/src/query/ir.ts:182](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L182) *** @@ -131,7 +131,7 @@ Defined in: [packages/db/src/query/ir.ts:145](https://github.com/TanStack/db/blo materialization: IncludesMaterialization; ``` -Defined in: [packages/db/src/query/ir.ts:148](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L148) +Defined in: [packages/db/src/query/ir.ts:185](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L185) *** @@ -141,7 +141,7 @@ Defined in: [packages/db/src/query/ir.ts:148](https://github.com/TanStack/db/blo optional parentFilters: Where[]; ``` -Defined in: [packages/db/src/query/ir.ts:146](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L146) +Defined in: [packages/db/src/query/ir.ts:183](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L183) *** @@ -151,7 +151,7 @@ Defined in: [packages/db/src/query/ir.ts:146](https://github.com/TanStack/db/blo optional parentProjection: PropRef[]; ``` -Defined in: [packages/db/src/query/ir.ts:147](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L147) +Defined in: [packages/db/src/query/ir.ts:184](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L184) *** @@ -161,7 +161,7 @@ Defined in: [packages/db/src/query/ir.ts:147](https://github.com/TanStack/db/blo query: QueryIR; ``` -Defined in: [packages/db/src/query/ir.ts:142](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L142) +Defined in: [packages/db/src/query/ir.ts:179](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L179) *** @@ -171,7 +171,7 @@ Defined in: [packages/db/src/query/ir.ts:142](https://github.com/TanStack/db/blo optional scalarField: string; ``` -Defined in: [packages/db/src/query/ir.ts:149](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L149) +Defined in: [packages/db/src/query/ir.ts:186](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L186) *** @@ -181,7 +181,7 @@ Defined in: [packages/db/src/query/ir.ts:149](https://github.com/TanStack/db/blo type: "includesSubquery"; ``` -Defined in: [packages/db/src/query/ir.ts:140](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L140) +Defined in: [packages/db/src/query/ir.ts:177](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L177) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md b/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md index f9dd66cf12..734b812c81 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md @@ -5,7 +5,7 @@ title: PropRef # Class: PropRef\ -Defined in: [packages/db/src/query/ir.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L96) +Defined in: [packages/db/src/query/ir.ts:133](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L133) ## Extends @@ -25,7 +25,7 @@ Defined in: [packages/db/src/query/ir.ts:96](https://github.com/TanStack/db/blob new PropRef(path): PropRef; ``` -Defined in: [packages/db/src/query/ir.ts:98](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L98) +Defined in: [packages/db/src/query/ir.ts:135](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L135) #### Parameters @@ -51,7 +51,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) **`Internal`** @@ -71,7 +71,7 @@ BaseExpression.__returnType path: string[]; ``` -Defined in: [packages/db/src/query/ir.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L99) +Defined in: [packages/db/src/query/ir.ts:136](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L136) *** @@ -81,7 +81,7 @@ Defined in: [packages/db/src/query/ir.ts:99](https://github.com/TanStack/db/blob type: "ref"; ``` -Defined in: [packages/db/src/query/ir.ts:97](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L97) +Defined in: [packages/db/src/query/ir.ts:134](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L134) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md b/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md index 1dbc0357e9..7f52841e65 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md @@ -5,7 +5,7 @@ title: QueryRef # Class: QueryRef -Defined in: [packages/db/src/query/ir.ts:86](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L86) +Defined in: [packages/db/src/query/ir.ts:95](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L95) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/query/ir.ts:86](https://github.com/TanStack/db/blob new QueryRef(query, alias): QueryRef; ``` -Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) +Defined in: [packages/db/src/query/ir.ts:97](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L97) #### Parameters @@ -49,7 +49,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) **`Internal`** @@ -69,7 +69,7 @@ BaseExpression.__returnType alias: string; ``` -Defined in: [packages/db/src/query/ir.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L90) +Defined in: [packages/db/src/query/ir.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L99) *** @@ -79,7 +79,7 @@ Defined in: [packages/db/src/query/ir.ts:90](https://github.com/TanStack/db/blob query: QueryIR; ``` -Defined in: [packages/db/src/query/ir.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L89) +Defined in: [packages/db/src/query/ir.ts:98](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L98) *** @@ -89,7 +89,7 @@ Defined in: [packages/db/src/query/ir.ts:89](https://github.com/TanStack/db/blob type: "queryRef"; ``` -Defined in: [packages/db/src/query/ir.ts:87](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L87) +Defined in: [packages/db/src/query/ir.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L96) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md b/docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md new file mode 100644 index 0000000000..8e119b7c0d --- /dev/null +++ b/docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md @@ -0,0 +1,105 @@ +--- +id: UnionAll +title: UnionAll +--- + +# Class: UnionAll + +Defined in: [packages/db/src/query/ir.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L116) + +## Extends + +- `BaseExpression` + +## Constructors + +### Constructor + +```ts +new UnionAll(queries): UnionAll; +``` + +Defined in: [packages/db/src/query/ir.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L124) + +Result-level UNION ALL. Downstream query clauses see the union result row +shape, not the branch source aliases. Optimizers may push safe operations +into branches, but compiler phases should treat this as a derived relation +unless they are explicitly handling branch lowering. + +#### Parameters + +##### queries + +[`QueryIR`](../interfaces/QueryIR.md)[] + +#### Returns + +`UnionAll` + +#### Overrides + +```ts +BaseExpression.constructor +``` + +## Properties + +### \_\_returnType + +```ts +readonly __returnType: any; +``` + +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) + +**`Internal`** + +- Type brand for TypeScript inference + +#### Inherited from + +```ts +BaseExpression.__returnType +``` + +*** + +### queries + +```ts +queries: QueryIR[]; +``` + +Defined in: [packages/db/src/query/ir.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L124) + +*** + +### type + +```ts +type: "unionAll"; +``` + +Defined in: [packages/db/src/query/ir.ts:117](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L117) + +#### Overrides + +```ts +BaseExpression.type +``` + +## Accessors + +### alias + +#### Get Signature + +```ts +get alias(): string; +``` + +Defined in: [packages/db/src/query/ir.ts:128](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L128) + +##### Returns + +`string` diff --git a/docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md b/docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md new file mode 100644 index 0000000000..f15085397a --- /dev/null +++ b/docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md @@ -0,0 +1,100 @@ +--- +id: UnionFrom +title: UnionFrom +--- + +# Class: UnionFrom + +Defined in: [packages/db/src/query/ir.ts:105](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L105) + +## Extends + +- `BaseExpression` + +## Constructors + +### Constructor + +```ts +new UnionFrom(sources): UnionFrom; +``` + +Defined in: [packages/db/src/query/ir.ts:107](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L107) + +#### Parameters + +##### sources + +([`CollectionRef`](CollectionRef.md) \| [`QueryRef`](QueryRef.md))[] + +#### Returns + +`UnionFrom` + +#### Overrides + +```ts +BaseExpression.constructor +``` + +## Properties + +### \_\_returnType + +```ts +readonly __returnType: any; +``` + +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) + +**`Internal`** + +- Type brand for TypeScript inference + +#### Inherited from + +```ts +BaseExpression.__returnType +``` + +*** + +### sources + +```ts +sources: (CollectionRef | QueryRef)[]; +``` + +Defined in: [packages/db/src/query/ir.ts:107](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L107) + +*** + +### type + +```ts +type: "unionFrom"; +``` + +Defined in: [packages/db/src/query/ir.ts:106](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L106) + +#### Overrides + +```ts +BaseExpression.type +``` + +## Accessors + +### alias + +#### Get Signature + +```ts +get alias(): string; +``` + +Defined in: [packages/db/src/query/ir.ts:111](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L111) + +##### Returns + +`string` diff --git a/docs/reference/@tanstack/namespaces/IR/classes/Value.md b/docs/reference/@tanstack/namespaces/IR/classes/Value.md index fd1970ef7a..e5adbed4b5 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/Value.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/Value.md @@ -5,7 +5,7 @@ title: Value # Class: Value\ -Defined in: [packages/db/src/query/ir.ts:105](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L105) +Defined in: [packages/db/src/query/ir.ts:142](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L142) ## Extends @@ -25,7 +25,7 @@ Defined in: [packages/db/src/query/ir.ts:105](https://github.com/TanStack/db/blo new Value(value): Value; ``` -Defined in: [packages/db/src/query/ir.ts:107](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L107) +Defined in: [packages/db/src/query/ir.ts:144](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L144) #### Parameters @@ -51,7 +51,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L82) **`Internal`** @@ -71,7 +71,7 @@ BaseExpression.__returnType type: "val"; ``` -Defined in: [packages/db/src/query/ir.ts:106](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L106) +Defined in: [packages/db/src/query/ir.ts:143](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L143) #### Overrides @@ -87,4 +87,4 @@ BaseExpression.type value: T; ``` -Defined in: [packages/db/src/query/ir.ts:108](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L108) +Defined in: [packages/db/src/query/ir.ts:145](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L145) diff --git a/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md b/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md index e4c71d86f1..95c6d17f41 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md @@ -9,7 +9,7 @@ title: createResidualWhere function createResidualWhere(expression): Where; ``` -Defined in: [packages/db/src/query/ir.ts:208](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L208) +Defined in: [packages/db/src/query/ir.ts:296](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L296) Create a residual Where clause from an expression diff --git a/docs/reference/@tanstack/namespaces/IR/functions/followRef.md b/docs/reference/@tanstack/namespaces/IR/functions/followRef.md index 5d2636f8c3..4b356998f3 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/followRef.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/followRef.md @@ -17,7 +17,7 @@ function followRef( }; ``` -Defined in: [packages/db/src/query/ir.ts:234](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L234) +Defined in: [packages/db/src/query/ir.ts:328](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L328) Follows the given reference in a query until its finds the root field the reference points to. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md b/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md index 1ce31a66a9..5ec8e22c4c 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md @@ -11,7 +11,7 @@ function getHavingExpression(having): | Aggregate; ``` -Defined in: [packages/db/src/query/ir.ts:186](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L186) +Defined in: [packages/db/src/query/ir.ts:274](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L274) Extract the expression from a HAVING clause HAVING clauses can contain aggregates, unlike regular WHERE clauses diff --git a/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md b/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md index fbf8d49943..1d4e6ad699 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md @@ -9,7 +9,7 @@ title: getWhereExpression function getWhereExpression(where): BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:176](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L176) +Defined in: [packages/db/src/query/ir.ts:264](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L264) Extract the expression from a Where clause diff --git a/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md b/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md index 07961d0d13..5f158b4abc 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md @@ -9,7 +9,7 @@ title: isExpressionLike function isExpressionLike(value): boolean; ``` -Defined in: [packages/db/src/query/ir.ts:159](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L159) +Defined in: [packages/db/src/query/ir.ts:218](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L218) Runtime helper to detect IR expression-like objects. Prefer this over ad-hoc local implementations to keep behavior consistent. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md b/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md index ff3b6b22f8..b8f61883eb 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md @@ -9,7 +9,7 @@ title: isResidualWhere function isResidualWhere(where): boolean; ``` -Defined in: [packages/db/src/query/ir.ts:197](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L197) +Defined in: [packages/db/src/query/ir.ts:285](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L285) Check if a Where clause is marked as residual diff --git a/docs/reference/@tanstack/namespaces/IR/index.md b/docs/reference/@tanstack/namespaces/IR/index.md index 415a365542..add67e808e 100644 --- a/docs/reference/@tanstack/namespaces/IR/index.md +++ b/docs/reference/@tanstack/namespaces/IR/index.md @@ -9,10 +9,13 @@ title: IR - [Aggregate](classes/Aggregate.md) - [CollectionRef](classes/CollectionRef.md) +- [ConditionalSelect](classes/ConditionalSelect.md) - [Func](classes/Func.md) - [IncludesSubquery](classes/IncludesSubquery.md) - [PropRef](classes/PropRef.md) - [QueryRef](classes/QueryRef.md) +- [UnionAll](classes/UnionAll.md) +- [UnionFrom](classes/UnionFrom.md) - [Value](classes/Value.md) ## Interfaces @@ -23,6 +26,7 @@ title: IR ## Type Aliases - [BasicExpression](type-aliases/BasicExpression.md) +- [ConditionalSelectBranch](type-aliases/ConditionalSelectBranch.md) - [From](type-aliases/From.md) - [GroupBy](type-aliases/GroupBy.md) - [Having](type-aliases/Having.md) @@ -34,6 +38,7 @@ title: IR - [OrderByClause](type-aliases/OrderByClause.md) - [OrderByDirection](type-aliases/OrderByDirection.md) - [Select](type-aliases/Select.md) +- [SelectValueExpression](type-aliases/SelectValueExpression.md) - [Where](type-aliases/Where.md) ## Variables diff --git a/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md b/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md index e41a4e7153..d41b72cace 100644 --- a/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md +++ b/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md @@ -5,7 +5,7 @@ title: JoinClause # Interface: JoinClause -Defined in: [packages/db/src/query/ir.ts:40](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L40) +Defined in: [packages/db/src/query/ir.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L49) ## Properties @@ -17,7 +17,7 @@ from: | QueryRef; ``` -Defined in: [packages/db/src/query/ir.ts:41](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L41) +Defined in: [packages/db/src/query/ir.ts:50](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L50) *** @@ -27,7 +27,7 @@ Defined in: [packages/db/src/query/ir.ts:41](https://github.com/TanStack/db/blob left: BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:43](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L43) +Defined in: [packages/db/src/query/ir.ts:52](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L52) *** @@ -37,7 +37,7 @@ Defined in: [packages/db/src/query/ir.ts:43](https://github.com/TanStack/db/blob right: BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:44](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L44) +Defined in: [packages/db/src/query/ir.ts:53](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L53) *** @@ -47,4 +47,4 @@ Defined in: [packages/db/src/query/ir.ts:44](https://github.com/TanStack/db/blob type: "inner" | "left" | "right" | "full" | "outer" | "cross"; ``` -Defined in: [packages/db/src/query/ir.ts:42](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L42) +Defined in: [packages/db/src/query/ir.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L51) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md index de4676e5b7..8d15913c05 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md @@ -12,7 +12,7 @@ type BasicExpression = | Func; ``` -Defined in: [packages/db/src/query/ir.ts:127](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L127) +Defined in: [packages/db/src/query/ir.ts:164](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L164) ## Type Parameters diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md new file mode 100644 index 0000000000..73c4f155d0 --- /dev/null +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md @@ -0,0 +1,32 @@ +--- +id: ConditionalSelectBranch +title: ConditionalSelectBranch +--- + +# Type Alias: ConditionalSelectBranch + +```ts +type ConditionalSelectBranch = object; +``` + +Defined in: [packages/db/src/query/ir.ts:192](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L192) + +## Properties + +### condition + +```ts +condition: BasicExpression; +``` + +Defined in: [packages/db/src/query/ir.ts:193](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L193) + +*** + +### value + +```ts +value: SelectValueExpression; +``` + +Defined in: [packages/db/src/query/ir.ts:194](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L194) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md index f5dc25e149..4b863b7a95 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md @@ -8,7 +8,9 @@ title: From ```ts type From = | CollectionRef - | QueryRef; + | QueryRef + | UnionFrom + | UnionAll; ``` -Defined in: [packages/db/src/query/ir.ts:32](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L32) +Defined in: [packages/db/src/query/ir.ts:36](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L36) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md index 1ecd399968..e17b6a1c8d 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md @@ -9,4 +9,4 @@ title: GroupBy type GroupBy = BasicExpression[]; ``` -Defined in: [packages/db/src/query/ir.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L51) +Defined in: [packages/db/src/query/ir.ts:60](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L60) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md index 7654798e3a..2f042ce4dd 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md @@ -9,4 +9,4 @@ title: Having type Having = Where; ``` -Defined in: [packages/db/src/query/ir.ts:53](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L53) +Defined in: [packages/db/src/query/ir.ts:62](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L62) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md index 02afe1663a..578786d243 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md @@ -6,7 +6,7 @@ title: IncludesMaterialization # Type Alias: IncludesMaterialization ```ts -type IncludesMaterialization = "collection" | "array" | "concat"; +type IncludesMaterialization = "collection" | "array" | "singleton" | "concat"; ``` Defined in: [packages/db/src/query/ir.ts:28](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L28) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md index 53b84b8b49..0e6c1d1b90 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md @@ -9,4 +9,4 @@ title: Join type Join = JoinClause[]; ``` -Defined in: [packages/db/src/query/ir.ts:38](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L38) +Defined in: [packages/db/src/query/ir.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L47) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md index 38be20e012..b571ea0440 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md @@ -9,4 +9,4 @@ title: Limit type Limit = number; ``` -Defined in: [packages/db/src/query/ir.ts:64](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L64) +Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md index 771c53d41a..a95aa02514 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md @@ -9,4 +9,4 @@ title: Offset type Offset = number; ``` -Defined in: [packages/db/src/query/ir.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L66) +Defined in: [packages/db/src/query/ir.ts:75](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L75) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md index 2d5a450cdf..14ce76dc14 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md @@ -9,4 +9,4 @@ title: OrderBy type OrderBy = OrderByClause[]; ``` -Defined in: [packages/db/src/query/ir.ts:55](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L55) +Defined in: [packages/db/src/query/ir.ts:64](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L64) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md index 2770aed38b..987cbf522b 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md @@ -9,7 +9,7 @@ title: OrderByClause type OrderByClause = object; ``` -Defined in: [packages/db/src/query/ir.ts:57](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L57) +Defined in: [packages/db/src/query/ir.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L66) ## Properties @@ -19,7 +19,7 @@ Defined in: [packages/db/src/query/ir.ts:57](https://github.com/TanStack/db/blob compareOptions: CompareOptions; ``` -Defined in: [packages/db/src/query/ir.ts:59](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L59) +Defined in: [packages/db/src/query/ir.ts:68](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L68) *** @@ -29,4 +29,4 @@ Defined in: [packages/db/src/query/ir.ts:59](https://github.com/TanStack/db/blob expression: BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L58) +Defined in: [packages/db/src/query/ir.ts:67](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L67) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md index 58c632a106..7466f4b263 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md @@ -9,4 +9,4 @@ title: OrderByDirection type OrderByDirection = "asc" | "desc"; ``` -Defined in: [packages/db/src/query/ir.ts:62](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L62) +Defined in: [packages/db/src/query/ir.ts:71](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L71) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md index 4016ef6f4d..3833cf0ed8 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md @@ -9,7 +9,7 @@ title: Select type Select = object; ``` -Defined in: [packages/db/src/query/ir.ts:34](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L34) +Defined in: [packages/db/src/query/ir.ts:38](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L38) ## Index Signature @@ -19,4 +19,5 @@ Defined in: [packages/db/src/query/ir.ts:34](https://github.com/TanStack/db/blob | Select | Aggregate | IncludesSubquery + | ConditionalSelect ``` diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md new file mode 100644 index 0000000000..c8f003863a --- /dev/null +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md @@ -0,0 +1,17 @@ +--- +id: SelectValueExpression +title: SelectValueExpression +--- + +# Type Alias: SelectValueExpression + +```ts +type SelectValueExpression = + | BasicExpression + | Aggregate + | Select + | IncludesSubquery + | ConditionalSelect; +``` + +Defined in: [packages/db/src/query/ir.ts:197](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L197) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md index fdbb878874..f80a99c4e7 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md @@ -14,4 +14,4 @@ type Where = }; ``` -Defined in: [packages/db/src/query/ir.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L47) +Defined in: [packages/db/src/query/ir.ts:56](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L56) diff --git a/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md b/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md index c68802f241..ea124c83e6 100644 --- a/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md +++ b/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md @@ -9,4 +9,4 @@ title: INCLUDES_SCALAR_FIELD const INCLUDES_SCALAR_FIELD: "__includes_scalar__"; ``` -Defined in: [packages/db/src/query/ir.ts:30](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L30) +Defined in: [packages/db/src/query/ir.ts:34](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L34) diff --git a/docs/reference/classes/AggregateFunctionNotInSelectError.md b/docs/reference/classes/AggregateFunctionNotInSelectError.md index 19e08fb682..0cbd5ea3cb 100644 --- a/docs/reference/classes/AggregateFunctionNotInSelectError.md +++ b/docs/reference/classes/AggregateFunctionNotInSelectError.md @@ -5,7 +5,7 @@ title: AggregateFunctionNotInSelectError # Class: AggregateFunctionNotInSelectError -Defined in: [packages/db/src/errors.ts:614](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L614) +Defined in: [packages/db/src/errors.ts:629](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L629) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:614](https://github.com/TanStack/db/blob/ new AggregateFunctionNotInSelectError(functionName): AggregateFunctionNotInSelectError; ``` -Defined in: [packages/db/src/errors.ts:615](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L615) +Defined in: [packages/db/src/errors.ts:630](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L630) #### Parameters diff --git a/docs/reference/classes/AggregateNotSupportedError.md b/docs/reference/classes/AggregateNotSupportedError.md index 7cdcf25e56..739ade6c1a 100644 --- a/docs/reference/classes/AggregateNotSupportedError.md +++ b/docs/reference/classes/AggregateNotSupportedError.md @@ -5,7 +5,7 @@ title: AggregateNotSupportedError # Class: AggregateNotSupportedError -Defined in: [packages/db/src/errors.ts:730](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L730) +Defined in: [packages/db/src/errors.ts:745](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L745) Error thrown when aggregate expressions are used outside of a GROUP BY context. @@ -21,7 +21,7 @@ Error thrown when aggregate expressions are used outside of a GROUP BY context. new AggregateNotSupportedError(): AggregateNotSupportedError; ``` -Defined in: [packages/db/src/errors.ts:731](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L731) +Defined in: [packages/db/src/errors.ts:746](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L746) #### Returns diff --git a/docs/reference/classes/BaseQueryBuilder.md b/docs/reference/classes/BaseQueryBuilder.md index 224e68885d..6e8842faf3 100644 --- a/docs/reference/classes/BaseQueryBuilder.md +++ b/docs/reference/classes/BaseQueryBuilder.md @@ -5,7 +5,7 @@ title: BaseQueryBuilder # Class: BaseQueryBuilder\ -Defined in: [packages/db/src/query/builder/index.ts:63](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L63) +Defined in: [packages/db/src/query/builder/index.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L78) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [packages/db/src/query/builder/index.ts:63](https://github.com/TanSt new BaseQueryBuilder(query): BaseQueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L66) +Defined in: [packages/db/src/query/builder/index.ts:81](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L81) #### Parameters @@ -43,7 +43,7 @@ Defined in: [packages/db/src/query/builder/index.ts:66](https://github.com/TanSt get fn(): object; ``` -Defined in: [packages/db/src/query/builder/index.ts:770](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L770) +Defined in: [packages/db/src/query/builder/index.ts:850](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L850) Functional variants of the query builder These are imperative function that are called for ery row. @@ -176,7 +176,7 @@ query _getQuery(): QueryIR; ``` -Defined in: [packages/db/src/query/builder/index.ts:857](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L857) +Defined in: [packages/db/src/query/builder/index.ts:937](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L937) #### Returns @@ -190,7 +190,7 @@ Defined in: [packages/db/src/query/builder/index.ts:857](https://github.com/TanS distinct(): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:709](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L709) +Defined in: [packages/db/src/query/builder/index.ts:783](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L783) Specify that the query should return distinct rows. Deduplicates rows based on the selected columns. @@ -219,7 +219,7 @@ query findOne(): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:729](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L729) +Defined in: [packages/db/src/query/builder/index.ts:803](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L803) Specify that the query should return a single result @@ -244,15 +244,10 @@ query ### from() ```ts -from(source): QueryBuilder<{ - baseSchema: SchemaFromSource; - fromSourceName: keyof TSource & string; - hasJoins: false; - schema: SchemaFromSource; -}>; +from(source): QueryBuilder>; ``` -Defined in: [packages/db/src/query/builder/index.ts:145](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L145) +Defined in: [packages/db/src/query/builder/index.ts:175](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L175) Specify the source table or subquery for the query @@ -266,18 +261,13 @@ Specify the source table or subquery for the query ##### source -`TSource` +[`SingleSource`](../type-aliases/SingleSource.md)\<`TSource`\> An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery #### Returns -[`QueryBuilder`](../type-aliases/QueryBuilder.md)\<\{ - `baseSchema`: [`SchemaFromSource`](../type-aliases/SchemaFromSource.md)\<`TSource`\>; - `fromSourceName`: keyof `TSource` & `string`; - `hasJoins`: `false`; - `schema`: [`SchemaFromSource`](../type-aliases/SchemaFromSource.md)\<`TSource`\>; -\}\> +[`QueryBuilder`](../type-aliases/QueryBuilder.md)\<[`ContextFromSource`](../type-aliases/ContextFromSource.md)\<`TSource`\>\> A QueryBuilder with the specified source @@ -300,7 +290,7 @@ query.from({ activeUsers }) fullJoin(source, onCallback): QueryBuilder, "full">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:336](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L336) +Defined in: [packages/db/src/query/builder/index.ts:410](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L410) Perform a FULL JOIN with another table or subquery @@ -347,7 +337,7 @@ query groupBy(callback): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:631](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L631) +Defined in: [packages/db/src/query/builder/index.ts:705](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L705) Group rows by one or more columns for aggregation @@ -396,7 +386,7 @@ query having(callback): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:430](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L430) +Defined in: [packages/db/src/query/builder/index.ts:504](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L504) Filter grouped rows based on aggregate conditions @@ -445,7 +435,7 @@ query innerJoin(source, onCallback): QueryBuilder, "inner">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:310](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L310) +Defined in: [packages/db/src/query/builder/index.ts:384](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L384) Perform an INNER JOIN with another table or subquery @@ -495,7 +485,7 @@ join( type): QueryBuilder, TJoinType>>; ``` -Defined in: [packages/db/src/query/builder/index.ts:188](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L188) +Defined in: [packages/db/src/query/builder/index.ts:262](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L262) Join another table or subquery to the current query @@ -563,7 +553,7 @@ query leftJoin(source, onCallback): QueryBuilder, "left">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:258](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L258) +Defined in: [packages/db/src/query/builder/index.ts:332](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L332) Perform a LEFT JOIN with another table or subquery @@ -610,7 +600,7 @@ query limit(count): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:664](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L664) +Defined in: [packages/db/src/query/builder/index.ts:738](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L738) Limit the number of rows returned by the query `orderBy` is required for `limit` @@ -647,7 +637,7 @@ query offset(count): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:688](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L688) +Defined in: [packages/db/src/query/builder/index.ts:762](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L762) Skip a number of rows before returning results `orderBy` is required for `offset` @@ -685,7 +675,7 @@ query orderBy(callback, options): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:555](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L555) +Defined in: [packages/db/src/query/builder/index.ts:629](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L629) Sort the query results by one or more columns @@ -735,7 +725,7 @@ query rightJoin(source, onCallback): QueryBuilder, "right">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:284](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L284) +Defined in: [packages/db/src/query/builder/index.ts:358](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L358) Perform a RIGHT JOIN with another table or subquery @@ -784,7 +774,7 @@ query select(callback): QueryBuilder>>; ``` -Defined in: [packages/db/src/query/builder/index.ts:496](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L496) +Defined in: [packages/db/src/query/builder/index.ts:570](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L570) Select specific columns or computed values from the query @@ -843,7 +833,7 @@ query select(callback): QueryBuilder>>; ``` -Defined in: [packages/db/src/query/builder/index.ts:501](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L501) +Defined in: [packages/db/src/query/builder/index.ts:575](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L575) Select specific columns or computed values from the query @@ -898,13 +888,95 @@ query *** +### unionAll() + +#### Call Signature + +```ts +unionAll(source): QueryBuilder>; +``` + +Defined in: [packages/db/src/query/builder/index.ts:201](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L201) + +Union multiple independent source streams in one query. + +##### Type Parameters + +###### TSource + +`TSource` *extends* [`Source`](../type-aliases/Source.md) + +##### Parameters + +###### source + +`TSource` + +An object with one or more aliases mapped to collections or subqueries + +##### Returns + +[`QueryBuilder`](../type-aliases/QueryBuilder.md)\<[`ContextFromUnionSource`](../type-aliases/ContextFromUnionSource.md)\<`TSource`\>\> + +A QueryBuilder with the unioned sources available + +##### Example + +```ts +query + .unionAll({ message: messagesCollection, toolCall: toolCallsCollection }) + .orderBy(({ message, toolCall }) => + coalesce(message.timestamp, toolCall.timestamp) + ) +``` + +#### Call Signature + +```ts +unionAll(...branches): QueryBuilder>; +``` + +Defined in: [packages/db/src/query/builder/index.ts:204](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L204) + +Union multiple independent source streams in one query. + +##### Type Parameters + +###### TBranches + +`TBranches` *extends* readonly \[[`QueryBuilder`](../type-aliases/QueryBuilder.md)\<`any`\>, [`QueryBuilder`](../type-aliases/QueryBuilder.md)\<`any`\>\] + +##### Parameters + +###### branches + +...`TBranches` + +##### Returns + +[`QueryBuilder`](../type-aliases/QueryBuilder.md)\<[`ContextFromUnionBranches`](../type-aliases/ContextFromUnionBranches.md)\<`TBranches`\>\> + +A QueryBuilder with the unioned sources available + +##### Example + +```ts +query + .unionAll({ message: messagesCollection, toolCall: toolCallsCollection }) + .orderBy(({ message, toolCall }) => + coalesce(message.timestamp, toolCall.timestamp) + ) +``` + +*** + ### where() ```ts where(callback): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:375](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L375) +Defined in: [packages/db/src/query/builder/index.ts:449](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L449) Filter rows based on a condition diff --git a/docs/reference/classes/CannotCombineEmptyExpressionListError.md b/docs/reference/classes/CannotCombineEmptyExpressionListError.md index 121f7481d0..40b7f9be02 100644 --- a/docs/reference/classes/CannotCombineEmptyExpressionListError.md +++ b/docs/reference/classes/CannotCombineEmptyExpressionListError.md @@ -5,7 +5,7 @@ title: CannotCombineEmptyExpressionListError # Class: CannotCombineEmptyExpressionListError -Defined in: [packages/db/src/errors.ts:693](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L693) +Defined in: [packages/db/src/errors.ts:708](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L708) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:693](https://github.com/TanStack/db/blob/ new CannotCombineEmptyExpressionListError(): CannotCombineEmptyExpressionListError; ``` -Defined in: [packages/db/src/errors.ts:694](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L694) +Defined in: [packages/db/src/errors.ts:709](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L709) #### Returns diff --git a/docs/reference/classes/CollectionImpl.md b/docs/reference/classes/CollectionImpl.md index 18c8c74d69..a287af2c24 100644 --- a/docs/reference/classes/CollectionImpl.md +++ b/docs/reference/classes/CollectionImpl.md @@ -5,7 +5,7 @@ title: CollectionImpl # Class: CollectionImpl\ -Defined in: [packages/db/src/collection/index.ts:272](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L272) +Defined in: [packages/db/src/collection/index.ts:273](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L273) ## Extended by @@ -42,7 +42,7 @@ Defined in: [packages/db/src/collection/index.ts:272](https://github.com/TanStac new CollectionImpl(config): CollectionImpl; ``` -Defined in: [packages/db/src/collection/index.ts:318](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L318) +Defined in: [packages/db/src/collection/index.ts:319](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L319) Creates a new Collection instance @@ -70,7 +70,7 @@ Error if sync config is missing _lifecycle: CollectionLifecycleManager; ``` -Defined in: [packages/db/src/collection/index.ts:289](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L289) +Defined in: [packages/db/src/collection/index.ts:290](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L290) *** @@ -80,7 +80,7 @@ Defined in: [packages/db/src/collection/index.ts:289](https://github.com/TanStac _state: CollectionStateManager; ``` -Defined in: [packages/db/src/collection/index.ts:301](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L301) +Defined in: [packages/db/src/collection/index.ts:302](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L302) *** @@ -90,7 +90,7 @@ Defined in: [packages/db/src/collection/index.ts:301](https://github.com/TanStac _sync: CollectionSyncManager; ``` -Defined in: [packages/db/src/collection/index.ts:290](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L290) +Defined in: [packages/db/src/collection/index.ts:291](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L291) *** @@ -100,7 +100,7 @@ Defined in: [packages/db/src/collection/index.ts:290](https://github.com/TanStac config: CollectionConfig; ``` -Defined in: [packages/db/src/collection/index.ts:280](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L280) +Defined in: [packages/db/src/collection/index.ts:281](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L281) *** @@ -110,7 +110,7 @@ Defined in: [packages/db/src/collection/index.ts:280](https://github.com/TanStac deferDataRefresh: Promise | null = null; ``` -Defined in: [packages/db/src/collection/index.ts:308](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L308) +Defined in: [packages/db/src/collection/index.ts:309](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L309) When set, collection consumers should defer processing incoming data refreshes until this promise resolves. This prevents stale data from @@ -124,7 +124,7 @@ overwriting optimistic state while pending writes are being applied. id: string; ``` -Defined in: [packages/db/src/collection/index.ts:279](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L279) +Defined in: [packages/db/src/collection/index.ts:280](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L280) *** @@ -134,7 +134,7 @@ Defined in: [packages/db/src/collection/index.ts:279](https://github.com/TanStac utils: Record = {}; ``` -Defined in: [packages/db/src/collection/index.ts:284](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L284) +Defined in: [packages/db/src/collection/index.ts:285](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L285) ## Accessors @@ -146,7 +146,7 @@ Defined in: [packages/db/src/collection/index.ts:284](https://github.com/TanStac get compareOptions(): StringCollationConfig; ``` -Defined in: [packages/db/src/collection/index.ts:643](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L643) +Defined in: [packages/db/src/collection/index.ts:644](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L644) ##### Returns @@ -162,7 +162,7 @@ Defined in: [packages/db/src/collection/index.ts:643](https://github.com/TanStac get indexes(): Map>; ``` -Defined in: [packages/db/src/collection/index.ts:628](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L628) +Defined in: [packages/db/src/collection/index.ts:629](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L629) Get resolved indexes for query optimization @@ -180,7 +180,7 @@ Get resolved indexes for query optimization get isLoadingSubset(): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:456](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L456) +Defined in: [packages/db/src/collection/index.ts:457](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L457) Check if the collection is currently loading more data @@ -200,7 +200,7 @@ true if the collection has pending load more operations, false otherwise get size(): number; ``` -Defined in: [packages/db/src/collection/index.ts:493](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L493) +Defined in: [packages/db/src/collection/index.ts:494](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L494) Get the current size of the collection (cached) @@ -218,7 +218,7 @@ Get the current size of the collection (cached) get state(): Map>; ``` -Defined in: [packages/db/src/collection/index.ts:820](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L820) +Defined in: [packages/db/src/collection/index.ts:821](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L821) Gets the current state of the collection as a Map @@ -254,7 +254,7 @@ Map containing all items in the collection, with keys as identifiers get status(): CollectionStatus; ``` -Defined in: [packages/db/src/collection/index.ts:411](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L411) +Defined in: [packages/db/src/collection/index.ts:412](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L412) Gets the current status of the collection @@ -272,7 +272,7 @@ Gets the current status of the collection get subscriberCount(): number; ``` -Defined in: [packages/db/src/collection/index.ts:418](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L418) +Defined in: [packages/db/src/collection/index.ts:419](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L419) Get the number of subscribers to the collection @@ -290,7 +290,7 @@ Get the number of subscribers to the collection get toArray(): WithVirtualProps[]; ``` -Defined in: [packages/db/src/collection/index.ts:849](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L849) +Defined in: [packages/db/src/collection/index.ts:850](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L850) Gets the current state of the collection as an Array @@ -308,7 +308,7 @@ An Array containing all items in the collection iterator: IterableIterator<[TKey, WithVirtualProps]>; ``` -Defined in: [packages/db/src/collection/index.ts:531](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L531) +Defined in: [packages/db/src/collection/index.ts:532](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L532) Get all entries (virtual derived state) @@ -324,7 +324,7 @@ Get all entries (virtual derived state) cleanup(): Promise; ``` -Defined in: [packages/db/src/collection/index.ts:988](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L988) +Defined in: [packages/db/src/collection/index.ts:989](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L989) Clean up the collection by stopping sync and clearing data This can be called manually or automatically by garbage collection @@ -341,7 +341,7 @@ This can be called manually or automatically by garbage collection createIndex(indexCallback, config): BaseIndex; ``` -Defined in: [packages/db/src/collection/index.ts:597](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L597) +Defined in: [packages/db/src/collection/index.ts:598](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L598) Creates an index on a collection for faster queries. Indexes significantly improve query performance by allowing constant time lookups @@ -397,7 +397,7 @@ currentStateAsChanges(options): | ChangeMessage, string | number>[]; ``` -Defined in: [packages/db/src/collection/index.ts:887](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L887) +Defined in: [packages/db/src/collection/index.ts:888](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L888) Returns the current state of the collection as an array of changes @@ -441,7 +441,7 @@ const activeChanges = collection.currentStateAsChanges({ delete(keys, config?): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:797](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L797) +Defined in: [packages/db/src/collection/index.ts:798](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L798) Deletes one or more items from the collection @@ -504,7 +504,7 @@ try { entries(): IterableIterator<[TKey, WithVirtualProps]>; ``` -Defined in: [packages/db/src/collection/index.ts:519](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L519) +Defined in: [packages/db/src/collection/index.ts:520](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L520) Get all entries (virtual derived state) @@ -520,7 +520,7 @@ Get all entries (virtual derived state) forEach(callbackfn): void; ``` -Defined in: [packages/db/src/collection/index.ts:540](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L540) +Defined in: [packages/db/src/collection/index.ts:541](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L541) Execute a callback for each entry in the collection @@ -544,7 +544,7 @@ get(key): | undefined; ``` -Defined in: [packages/db/src/collection/index.ts:479](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L479) +Defined in: [packages/db/src/collection/index.ts:480](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L480) Get the current value for a key (virtual derived state) @@ -567,7 +567,7 @@ Get the current value for a key (virtual derived state) getIndexMetadata(): CollectionIndexMetadata[]; ``` -Defined in: [packages/db/src/collection/index.ts:621](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L621) +Defined in: [packages/db/src/collection/index.ts:622](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L622) Returns a snapshot of current index metadata sorted by indexId. Persistence wrappers can use this to bootstrap index state if indexes were @@ -585,7 +585,7 @@ created before event listeners were attached. getKeyFromItem(item): TKey; ``` -Defined in: [packages/db/src/collection/index.ts:571](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L571) +Defined in: [packages/db/src/collection/index.ts:572](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L572) #### Parameters @@ -605,7 +605,7 @@ Defined in: [packages/db/src/collection/index.ts:571](https://github.com/TanStac has(key): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:486](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L486) +Defined in: [packages/db/src/collection/index.ts:487](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L487) Check if a key exists in the collection (virtual derived state) @@ -627,7 +627,7 @@ Check if a key exists in the collection (virtual derived state) insert(data, config?): Transaction>; ``` -Defined in: [packages/db/src/collection/index.ts:684](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L684) +Defined in: [packages/db/src/collection/index.ts:685](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L685) Inserts one or more items into the collection @@ -697,7 +697,7 @@ try { isReady(): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:448](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L448) +Defined in: [packages/db/src/collection/index.ts:449](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L449) Check if the collection is ready for use Returns true if the collection has been marked as ready by its sync implementation @@ -727,7 +727,7 @@ if (collection.isReady()) { keys(): IterableIterator; ``` -Defined in: [packages/db/src/collection/index.ts:500](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L500) +Defined in: [packages/db/src/collection/index.ts:501](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L501) Get all keys (virtual derived state) @@ -743,7 +743,7 @@ Get all keys (virtual derived state) map(callbackfn): U[]; ``` -Defined in: [packages/db/src/collection/index.ts:556](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L556) +Defined in: [packages/db/src/collection/index.ts:557](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L557) Create a new array with the results of calling a function for each entry in the collection @@ -771,7 +771,7 @@ Create a new array with the results of calling a function for each entry in the off(event, callback): void; ``` -Defined in: [packages/db/src/collection/index.ts:967](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L967) +Defined in: [packages/db/src/collection/index.ts:968](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L968) Unsubscribe from a collection event @@ -814,7 +814,7 @@ Unsubscribe from a collection event on(event, callback): () => void; ``` -Defined in: [packages/db/src/collection/index.ts:947](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L947) +Defined in: [packages/db/src/collection/index.ts:948](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L948) Subscribe to a collection event @@ -863,7 +863,7 @@ Subscribe to a collection event once(event, callback): () => void; ``` -Defined in: [packages/db/src/collection/index.ts:957](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L957) +Defined in: [packages/db/src/collection/index.ts:958](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L958) Subscribe to a collection event once @@ -912,7 +912,7 @@ Subscribe to a collection event once onFirstReady(callback): void; ``` -Defined in: [packages/db/src/collection/index.ts:432](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L432) +Defined in: [packages/db/src/collection/index.ts:433](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L433) Register a callback to be executed when the collection first becomes ready Useful for preloading collections @@ -946,7 +946,7 @@ collection.onFirstReady(() => { preload(): Promise; ``` -Defined in: [packages/db/src/collection/index.ts:472](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L472) +Defined in: [packages/db/src/collection/index.ts:473](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L473) Preload the collection data by starting sync if not already started Multiple concurrent calls will share the same promise @@ -963,7 +963,7 @@ Multiple concurrent calls will share the same promise removeIndex(indexOrId): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:612](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L612) +Defined in: [packages/db/src/collection/index.ts:613](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L613) Removes an index created with createIndex. Returns true when an index existed and was removed. @@ -990,7 +990,7 @@ as invalid after removal. startSyncImmediate(): void; ``` -Defined in: [packages/db/src/collection/index.ts:464](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L464) +Defined in: [packages/db/src/collection/index.ts:465](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L465) Start sync immediately - internal method for compiled queries This bypasses lazy loading for special cases like live query results @@ -1007,7 +1007,7 @@ This bypasses lazy loading for special cases like live query results stateWhenReady(): Promise>>; ``` -Defined in: [packages/db/src/collection/index.ts:834](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L834) +Defined in: [packages/db/src/collection/index.ts:835](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L835) Gets the current state of the collection as a Map, but only resolves when data is available Waits for the first sync commit to complete before resolving @@ -1026,7 +1026,7 @@ Promise that resolves to a Map containing all items in the collection subscribeChanges(callback, options): CollectionSubscription; ``` -Defined in: [packages/db/src/collection/index.ts:935](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L935) +Defined in: [packages/db/src/collection/index.ts:936](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L936) Subscribe to changes in the collection @@ -1101,7 +1101,7 @@ const subscription = collection.subscribeChanges((changes) => { toArrayWhenReady(): Promise[]>; ``` -Defined in: [packages/db/src/collection/index.ts:859](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L859) +Defined in: [packages/db/src/collection/index.ts:860](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L860) Gets the current state of the collection as an Array, but only resolves when data is available Waits for the first sync commit to complete before resolving @@ -1122,7 +1122,7 @@ Promise that resolves to an Array containing all items in the collection update(key, callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:729](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L729) +Defined in: [packages/db/src/collection/index.ts:730](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L730) Updates one or more items in the collection using a callback function @@ -1193,7 +1193,7 @@ update( callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:735](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L735) +Defined in: [packages/db/src/collection/index.ts:736](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L736) Updates one or more items in the collection using a callback function @@ -1267,7 +1267,7 @@ try { update(id, callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:742](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L742) +Defined in: [packages/db/src/collection/index.ts:743](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L743) Updates one or more items in the collection using a callback function @@ -1338,7 +1338,7 @@ update( callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:748](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L748) +Defined in: [packages/db/src/collection/index.ts:749](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L749) Updates one or more items in the collection using a callback function @@ -1415,7 +1415,7 @@ validateData( key?): TOutput; ``` -Defined in: [packages/db/src/collection/index.ts:635](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L635) +Defined in: [packages/db/src/collection/index.ts:636](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L636) Validates the data against the schema @@ -1445,7 +1445,7 @@ Validates the data against the schema values(): IterableIterator>; ``` -Defined in: [packages/db/src/collection/index.ts:507](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L507) +Defined in: [packages/db/src/collection/index.ts:508](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L508) Get all values (virtual derived state) @@ -1461,7 +1461,7 @@ Get all values (virtual derived state) waitFor(event, timeout?): Promise; ``` -Defined in: [packages/db/src/collection/index.ts:977](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L977) +Defined in: [packages/db/src/collection/index.ts:978](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L978) Wait for a collection event diff --git a/docs/reference/classes/CollectionInputNotFoundError.md b/docs/reference/classes/CollectionInputNotFoundError.md index b2d75b3f58..d818d15d28 100644 --- a/docs/reference/classes/CollectionInputNotFoundError.md +++ b/docs/reference/classes/CollectionInputNotFoundError.md @@ -5,7 +5,7 @@ title: CollectionInputNotFoundError # Class: CollectionInputNotFoundError -Defined in: [packages/db/src/errors.ts:474](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L474) +Defined in: [packages/db/src/errors.ts:489](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L489) Error thrown when a collection input stream is not found during query compilation. In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream. @@ -25,7 +25,7 @@ new CollectionInputNotFoundError( availableKeys?): CollectionInputNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:475](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L475) +Defined in: [packages/db/src/errors.ts:490](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L490) #### Parameters diff --git a/docs/reference/classes/DistinctRequiresSelectError.md b/docs/reference/classes/DistinctRequiresSelectError.md index c72c004535..ff286020b5 100644 --- a/docs/reference/classes/DistinctRequiresSelectError.md +++ b/docs/reference/classes/DistinctRequiresSelectError.md @@ -5,7 +5,7 @@ title: DistinctRequiresSelectError # Class: DistinctRequiresSelectError -Defined in: [packages/db/src/errors.ts:430](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L430) +Defined in: [packages/db/src/errors.ts:445](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L445) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:430](https://github.com/TanStack/db/blob/ new DistinctRequiresSelectError(): DistinctRequiresSelectError; ``` -Defined in: [packages/db/src/errors.ts:431](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L431) +Defined in: [packages/db/src/errors.ts:446](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L446) #### Returns diff --git a/docs/reference/classes/DuplicateAliasInSubqueryError.md b/docs/reference/classes/DuplicateAliasInSubqueryError.md index d32ef272f9..728f2c6947 100644 --- a/docs/reference/classes/DuplicateAliasInSubqueryError.md +++ b/docs/reference/classes/DuplicateAliasInSubqueryError.md @@ -5,7 +5,7 @@ title: DuplicateAliasInSubqueryError # Class: DuplicateAliasInSubqueryError -Defined in: [packages/db/src/errors.ts:495](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L495) +Defined in: [packages/db/src/errors.ts:510](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L510) Error thrown when a subquery uses the same alias as its parent query. This causes issues because parent and subquery would share the same input streams, @@ -23,7 +23,7 @@ leading to empty results or incorrect data (aggregation cross-leaking). new DuplicateAliasInSubqueryError(alias, parentAliases): DuplicateAliasInSubqueryError; ``` -Defined in: [packages/db/src/errors.ts:496](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L496) +Defined in: [packages/db/src/errors.ts:511](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L511) #### Parameters diff --git a/docs/reference/classes/EmptyReferencePathError.md b/docs/reference/classes/EmptyReferencePathError.md index 2b5583daa0..b81a7f9f31 100644 --- a/docs/reference/classes/EmptyReferencePathError.md +++ b/docs/reference/classes/EmptyReferencePathError.md @@ -5,7 +5,7 @@ title: EmptyReferencePathError # Class: EmptyReferencePathError -Defined in: [packages/db/src/errors.ts:518](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L518) +Defined in: [packages/db/src/errors.ts:533](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L533) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:518](https://github.com/TanStack/db/blob/ new EmptyReferencePathError(): EmptyReferencePathError; ``` -Defined in: [packages/db/src/errors.ts:519](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L519) +Defined in: [packages/db/src/errors.ts:534](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L534) #### Returns diff --git a/docs/reference/classes/FnSelectWithGroupByError.md b/docs/reference/classes/FnSelectWithGroupByError.md index 4886f6e568..5fd37e7515 100644 --- a/docs/reference/classes/FnSelectWithGroupByError.md +++ b/docs/reference/classes/FnSelectWithGroupByError.md @@ -5,7 +5,7 @@ title: FnSelectWithGroupByError # Class: FnSelectWithGroupByError -Defined in: [packages/db/src/errors.ts:436](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L436) +Defined in: [packages/db/src/errors.ts:451](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L451) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:436](https://github.com/TanStack/db/blob/ new FnSelectWithGroupByError(): FnSelectWithGroupByError; ``` -Defined in: [packages/db/src/errors.ts:437](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L437) +Defined in: [packages/db/src/errors.ts:452](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L452) #### Returns diff --git a/docs/reference/classes/GroupByError.md b/docs/reference/classes/GroupByError.md index c010300fba..84e2b2cecf 100644 --- a/docs/reference/classes/GroupByError.md +++ b/docs/reference/classes/GroupByError.md @@ -5,7 +5,7 @@ title: GroupByError # Class: GroupByError -Defined in: [packages/db/src/errors.ts:593](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L593) +Defined in: [packages/db/src/errors.ts:608](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L608) ## Extends @@ -26,7 +26,7 @@ Defined in: [packages/db/src/errors.ts:593](https://github.com/TanStack/db/blob/ new GroupByError(message): GroupByError; ``` -Defined in: [packages/db/src/errors.ts:594](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L594) +Defined in: [packages/db/src/errors.ts:609](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L609) #### Parameters diff --git a/docs/reference/classes/HavingRequiresGroupByError.md b/docs/reference/classes/HavingRequiresGroupByError.md index bbf7cdb8fe..ee34aa6483 100644 --- a/docs/reference/classes/HavingRequiresGroupByError.md +++ b/docs/reference/classes/HavingRequiresGroupByError.md @@ -5,7 +5,7 @@ title: HavingRequiresGroupByError # Class: HavingRequiresGroupByError -Defined in: [packages/db/src/errors.ts:456](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L456) +Defined in: [packages/db/src/errors.ts:471](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L471) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:456](https://github.com/TanStack/db/blob/ new HavingRequiresGroupByError(): HavingRequiresGroupByError; ``` -Defined in: [packages/db/src/errors.ts:457](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L457) +Defined in: [packages/db/src/errors.ts:472](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L472) #### Returns diff --git a/docs/reference/classes/InvalidJoinCondition.md b/docs/reference/classes/InvalidJoinCondition.md index 777521bdc4..f4404ddc16 100644 --- a/docs/reference/classes/InvalidJoinCondition.md +++ b/docs/reference/classes/InvalidJoinCondition.md @@ -5,7 +5,7 @@ title: InvalidJoinCondition # Class: InvalidJoinCondition -Defined in: [packages/db/src/errors.ts:580](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L580) +Defined in: [packages/db/src/errors.ts:595](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L595) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:580](https://github.com/TanStack/db/blob/ new InvalidJoinCondition(): InvalidJoinCondition; ``` -Defined in: [packages/db/src/errors.ts:581](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L581) +Defined in: [packages/db/src/errors.ts:596](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L596) #### Returns diff --git a/docs/reference/classes/InvalidJoinConditionLeftSourceError.md b/docs/reference/classes/InvalidJoinConditionLeftSourceError.md index 2c6c6257df..2e48bf18ba 100644 --- a/docs/reference/classes/InvalidJoinConditionLeftSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionLeftSourceError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionLeftSourceError # Class: InvalidJoinConditionLeftSourceError -Defined in: [packages/db/src/errors.ts:564](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L564) +Defined in: [packages/db/src/errors.ts:579](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L579) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:564](https://github.com/TanStack/db/blob/ new InvalidJoinConditionLeftSourceError(sourceAlias): InvalidJoinConditionLeftSourceError; ``` -Defined in: [packages/db/src/errors.ts:565](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L565) +Defined in: [packages/db/src/errors.ts:580](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L580) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionRightSourceError.md b/docs/reference/classes/InvalidJoinConditionRightSourceError.md index 92f28a9565..2c41a52807 100644 --- a/docs/reference/classes/InvalidJoinConditionRightSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionRightSourceError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionRightSourceError # Class: InvalidJoinConditionRightSourceError -Defined in: [packages/db/src/errors.ts:572](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L572) +Defined in: [packages/db/src/errors.ts:587](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L587) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:572](https://github.com/TanStack/db/blob/ new InvalidJoinConditionRightSourceError(sourceAlias): InvalidJoinConditionRightSourceError; ``` -Defined in: [packages/db/src/errors.ts:573](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L573) +Defined in: [packages/db/src/errors.ts:588](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L588) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionSameSourceError.md b/docs/reference/classes/InvalidJoinConditionSameSourceError.md index 8b20d62b36..221d32f470 100644 --- a/docs/reference/classes/InvalidJoinConditionSameSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionSameSourceError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionSameSourceError # Class: InvalidJoinConditionSameSourceError -Defined in: [packages/db/src/errors.ts:550](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L550) +Defined in: [packages/db/src/errors.ts:565](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L565) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:550](https://github.com/TanStack/db/blob/ new InvalidJoinConditionSameSourceError(sourceAlias): InvalidJoinConditionSameSourceError; ``` -Defined in: [packages/db/src/errors.ts:551](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L551) +Defined in: [packages/db/src/errors.ts:566](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L566) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md b/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md index 3978012ae8..c588f019da 100644 --- a/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md +++ b/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionSourceMismatchError # Class: InvalidJoinConditionSourceMismatchError -Defined in: [packages/db/src/errors.ts:558](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L558) +Defined in: [packages/db/src/errors.ts:573](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L573) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:558](https://github.com/TanStack/db/blob/ new InvalidJoinConditionSourceMismatchError(): InvalidJoinConditionSourceMismatchError; ``` -Defined in: [packages/db/src/errors.ts:559](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L559) +Defined in: [packages/db/src/errors.ts:574](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L574) #### Returns diff --git a/docs/reference/classes/InvalidSourceTypeError.md b/docs/reference/classes/InvalidSourceTypeError.md index 7128f7249f..b7479a9efd 100644 --- a/docs/reference/classes/InvalidSourceTypeError.md +++ b/docs/reference/classes/InvalidSourceTypeError.md @@ -5,7 +5,7 @@ title: InvalidSourceTypeError # Class: InvalidSourceTypeError -Defined in: [packages/db/src/errors.ts:388](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L388) +Defined in: [packages/db/src/errors.ts:393](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L393) ## Extends @@ -19,13 +19,13 @@ Defined in: [packages/db/src/errors.ts:388](https://github.com/TanStack/db/blob/ new InvalidSourceTypeError(context, type): InvalidSourceTypeError; ``` -Defined in: [packages/db/src/errors.ts:389](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L389) +Defined in: [packages/db/src/errors.ts:394](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L394) #### Parameters ##### context -`string` +[`SourceClauseContext`](../type-aliases/SourceClauseContext.md) ##### type diff --git a/docs/reference/classes/InvalidStorageDataFormatError.md b/docs/reference/classes/InvalidStorageDataFormatError.md index c7ec40856c..77a251d8d9 100644 --- a/docs/reference/classes/InvalidStorageDataFormatError.md +++ b/docs/reference/classes/InvalidStorageDataFormatError.md @@ -5,7 +5,7 @@ title: InvalidStorageDataFormatError # Class: InvalidStorageDataFormatError -Defined in: [packages/db/src/errors.ts:658](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L658) +Defined in: [packages/db/src/errors.ts:673](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L673) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:658](https://github.com/TanStack/db/blob/ new InvalidStorageDataFormatError(storageKey, key): InvalidStorageDataFormatError; ``` -Defined in: [packages/db/src/errors.ts:659](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L659) +Defined in: [packages/db/src/errors.ts:674](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L674) #### Parameters diff --git a/docs/reference/classes/InvalidStorageObjectFormatError.md b/docs/reference/classes/InvalidStorageObjectFormatError.md index d2d3cec6e8..293360e806 100644 --- a/docs/reference/classes/InvalidStorageObjectFormatError.md +++ b/docs/reference/classes/InvalidStorageObjectFormatError.md @@ -5,7 +5,7 @@ title: InvalidStorageObjectFormatError # Class: InvalidStorageObjectFormatError -Defined in: [packages/db/src/errors.ts:666](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L666) +Defined in: [packages/db/src/errors.ts:681](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L681) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:666](https://github.com/TanStack/db/blob/ new InvalidStorageObjectFormatError(storageKey): InvalidStorageObjectFormatError; ``` -Defined in: [packages/db/src/errors.ts:667](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L667) +Defined in: [packages/db/src/errors.ts:682](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L682) #### Parameters diff --git a/docs/reference/classes/InvalidWhereExpressionError.md b/docs/reference/classes/InvalidWhereExpressionError.md index 26649e0ce7..cc1f4e394f 100644 --- a/docs/reference/classes/InvalidWhereExpressionError.md +++ b/docs/reference/classes/InvalidWhereExpressionError.md @@ -5,7 +5,7 @@ title: InvalidWhereExpressionError # Class: InvalidWhereExpressionError -Defined in: [packages/db/src/errors.ts:409](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L409) +Defined in: [packages/db/src/errors.ts:424](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L424) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:409](https://github.com/TanStack/db/blob/ new InvalidWhereExpressionError(valueType): InvalidWhereExpressionError; ``` -Defined in: [packages/db/src/errors.ts:410](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L410) +Defined in: [packages/db/src/errors.ts:425](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L425) #### Parameters diff --git a/docs/reference/classes/JoinCollectionNotFoundError.md b/docs/reference/classes/JoinCollectionNotFoundError.md index 1268a7f58e..11b55471fd 100644 --- a/docs/reference/classes/JoinCollectionNotFoundError.md +++ b/docs/reference/classes/JoinCollectionNotFoundError.md @@ -5,7 +5,7 @@ title: JoinCollectionNotFoundError # Class: JoinCollectionNotFoundError -Defined in: [packages/db/src/errors.ts:530](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L530) +Defined in: [packages/db/src/errors.ts:545](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L545) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:530](https://github.com/TanStack/db/blob/ new JoinCollectionNotFoundError(collectionId): JoinCollectionNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:531](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L531) +Defined in: [packages/db/src/errors.ts:546](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L546) #### Parameters diff --git a/docs/reference/classes/JoinConditionMustBeEqualityError.md b/docs/reference/classes/JoinConditionMustBeEqualityError.md index fea19bb6b5..f8c95d2dff 100644 --- a/docs/reference/classes/JoinConditionMustBeEqualityError.md +++ b/docs/reference/classes/JoinConditionMustBeEqualityError.md @@ -5,7 +5,7 @@ title: JoinConditionMustBeEqualityError # Class: JoinConditionMustBeEqualityError -Defined in: [packages/db/src/errors.ts:397](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L397) +Defined in: [packages/db/src/errors.ts:412](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L412) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:397](https://github.com/TanStack/db/blob/ new JoinConditionMustBeEqualityError(): JoinConditionMustBeEqualityError; ``` -Defined in: [packages/db/src/errors.ts:398](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L398) +Defined in: [packages/db/src/errors.ts:413](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L413) #### Returns diff --git a/docs/reference/classes/JoinError.md b/docs/reference/classes/JoinError.md index c1154a4667..92ecf3288b 100644 --- a/docs/reference/classes/JoinError.md +++ b/docs/reference/classes/JoinError.md @@ -5,7 +5,7 @@ title: JoinError # Class: JoinError -Defined in: [packages/db/src/errors.ts:537](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L537) +Defined in: [packages/db/src/errors.ts:552](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L552) ## Extends @@ -29,7 +29,7 @@ Defined in: [packages/db/src/errors.ts:537](https://github.com/TanStack/db/blob/ new JoinError(message): JoinError; ``` -Defined in: [packages/db/src/errors.ts:538](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L538) +Defined in: [packages/db/src/errors.ts:553](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L553) #### Parameters diff --git a/docs/reference/classes/LimitOffsetRequireOrderByError.md b/docs/reference/classes/LimitOffsetRequireOrderByError.md index 6a41edba30..8a7a900522 100644 --- a/docs/reference/classes/LimitOffsetRequireOrderByError.md +++ b/docs/reference/classes/LimitOffsetRequireOrderByError.md @@ -5,7 +5,7 @@ title: LimitOffsetRequireOrderByError # Class: LimitOffsetRequireOrderByError -Defined in: [packages/db/src/errors.ts:462](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L462) +Defined in: [packages/db/src/errors.ts:477](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L477) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:462](https://github.com/TanStack/db/blob/ new LimitOffsetRequireOrderByError(): LimitOffsetRequireOrderByError; ``` -Defined in: [packages/db/src/errors.ts:463](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L463) +Defined in: [packages/db/src/errors.ts:478](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L478) #### Returns diff --git a/docs/reference/classes/LocalStorageCollectionError.md b/docs/reference/classes/LocalStorageCollectionError.md index 78725fca5a..55ebac4c72 100644 --- a/docs/reference/classes/LocalStorageCollectionError.md +++ b/docs/reference/classes/LocalStorageCollectionError.md @@ -5,7 +5,7 @@ title: LocalStorageCollectionError # Class: LocalStorageCollectionError -Defined in: [packages/db/src/errors.ts:645](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L645) +Defined in: [packages/db/src/errors.ts:660](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L660) ## Extends @@ -25,7 +25,7 @@ Defined in: [packages/db/src/errors.ts:645](https://github.com/TanStack/db/blob/ new LocalStorageCollectionError(message): LocalStorageCollectionError; ``` -Defined in: [packages/db/src/errors.ts:646](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L646) +Defined in: [packages/db/src/errors.ts:661](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L661) #### Parameters diff --git a/docs/reference/classes/MissingAliasInputsError.md b/docs/reference/classes/MissingAliasInputsError.md index 8e13aedf05..9a29300c13 100644 --- a/docs/reference/classes/MissingAliasInputsError.md +++ b/docs/reference/classes/MissingAliasInputsError.md @@ -5,7 +5,7 @@ title: MissingAliasInputsError # Class: MissingAliasInputsError -Defined in: [packages/db/src/errors.ts:742](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L742) +Defined in: [packages/db/src/errors.ts:757](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L757) Internal error when the compiler returns aliases that don't have corresponding input streams. This should never happen since all aliases come from user declarations. @@ -22,7 +22,7 @@ This should never happen since all aliases come from user declarations. new MissingAliasInputsError(missingAliases): MissingAliasInputsError; ``` -Defined in: [packages/db/src/errors.ts:743](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L743) +Defined in: [packages/db/src/errors.ts:758](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L758) #### Parameters diff --git a/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md b/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md index b34fd77838..ea6472fc5c 100644 --- a/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md +++ b/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md @@ -5,7 +5,7 @@ title: NonAggregateExpressionNotInGroupByError # Class: NonAggregateExpressionNotInGroupByError -Defined in: [packages/db/src/errors.ts:600](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L600) +Defined in: [packages/db/src/errors.ts:615](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L615) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:600](https://github.com/TanStack/db/blob/ new NonAggregateExpressionNotInGroupByError(alias): NonAggregateExpressionNotInGroupByError; ``` -Defined in: [packages/db/src/errors.ts:601](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L601) +Defined in: [packages/db/src/errors.ts:616](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L616) #### Parameters diff --git a/docs/reference/classes/QueryCompilationError.md b/docs/reference/classes/QueryCompilationError.md index abd2054dea..cd7d2180a2 100644 --- a/docs/reference/classes/QueryCompilationError.md +++ b/docs/reference/classes/QueryCompilationError.md @@ -5,7 +5,7 @@ title: QueryCompilationError # Class: QueryCompilationError -Defined in: [packages/db/src/errors.ts:423](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L423) +Defined in: [packages/db/src/errors.ts:438](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L438) ## Extends @@ -38,7 +38,7 @@ Defined in: [packages/db/src/errors.ts:423](https://github.com/TanStack/db/blob/ new QueryCompilationError(message): QueryCompilationError; ``` -Defined in: [packages/db/src/errors.ts:424](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L424) +Defined in: [packages/db/src/errors.ts:439](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L439) #### Parameters diff --git a/docs/reference/classes/QueryMustHaveFromClauseError.md b/docs/reference/classes/QueryMustHaveFromClauseError.md index de0baea892..c0ce14ddf8 100644 --- a/docs/reference/classes/QueryMustHaveFromClauseError.md +++ b/docs/reference/classes/QueryMustHaveFromClauseError.md @@ -5,7 +5,7 @@ title: QueryMustHaveFromClauseError # Class: QueryMustHaveFromClauseError -Defined in: [packages/db/src/errors.ts:403](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L403) +Defined in: [packages/db/src/errors.ts:418](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L418) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:403](https://github.com/TanStack/db/blob/ new QueryMustHaveFromClauseError(): QueryMustHaveFromClauseError; ``` -Defined in: [packages/db/src/errors.ts:404](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L404) +Defined in: [packages/db/src/errors.ts:419](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L419) #### Returns diff --git a/docs/reference/classes/QueryOptimizerError.md b/docs/reference/classes/QueryOptimizerError.md index f519315f23..c0c6289e61 100644 --- a/docs/reference/classes/QueryOptimizerError.md +++ b/docs/reference/classes/QueryOptimizerError.md @@ -5,7 +5,7 @@ title: QueryOptimizerError # Class: QueryOptimizerError -Defined in: [packages/db/src/errors.ts:686](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L686) +Defined in: [packages/db/src/errors.ts:701](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L701) ## Extends @@ -24,7 +24,7 @@ Defined in: [packages/db/src/errors.ts:686](https://github.com/TanStack/db/blob/ new QueryOptimizerError(message): QueryOptimizerError; ``` -Defined in: [packages/db/src/errors.ts:687](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L687) +Defined in: [packages/db/src/errors.ts:702](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L702) #### Parameters diff --git a/docs/reference/classes/SerializationError.md b/docs/reference/classes/SerializationError.md index 57d07991cd..5097030ef0 100644 --- a/docs/reference/classes/SerializationError.md +++ b/docs/reference/classes/SerializationError.md @@ -5,7 +5,7 @@ title: SerializationError # Class: SerializationError -Defined in: [packages/db/src/errors.ts:636](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L636) +Defined in: [packages/db/src/errors.ts:651](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L651) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:636](https://github.com/TanStack/db/blob/ new SerializationError(operation, originalError): SerializationError; ``` -Defined in: [packages/db/src/errors.ts:637](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L637) +Defined in: [packages/db/src/errors.ts:652](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L652) #### Parameters diff --git a/docs/reference/classes/SetWindowRequiresOrderByError.md b/docs/reference/classes/SetWindowRequiresOrderByError.md index 54e14e7ca9..ab3ba5ffb3 100644 --- a/docs/reference/classes/SetWindowRequiresOrderByError.md +++ b/docs/reference/classes/SetWindowRequiresOrderByError.md @@ -5,7 +5,7 @@ title: SetWindowRequiresOrderByError # Class: SetWindowRequiresOrderByError -Defined in: [packages/db/src/errors.ts:754](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L754) +Defined in: [packages/db/src/errors.ts:769](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L769) Error thrown when setWindow is called on a collection without an ORDER BY clause. @@ -21,7 +21,7 @@ Error thrown when setWindow is called on a collection without an ORDER BY clause new SetWindowRequiresOrderByError(): SetWindowRequiresOrderByError; ``` -Defined in: [packages/db/src/errors.ts:755](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L755) +Defined in: [packages/db/src/errors.ts:770](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L770) #### Returns diff --git a/docs/reference/classes/StorageError.md b/docs/reference/classes/StorageError.md index 503edc0846..fa9fafd553 100644 --- a/docs/reference/classes/StorageError.md +++ b/docs/reference/classes/StorageError.md @@ -5,7 +5,7 @@ title: StorageError # Class: StorageError -Defined in: [packages/db/src/errors.ts:629](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L629) +Defined in: [packages/db/src/errors.ts:644](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L644) ## Extends @@ -24,7 +24,7 @@ Defined in: [packages/db/src/errors.ts:629](https://github.com/TanStack/db/blob/ new StorageError(message): StorageError; ``` -Defined in: [packages/db/src/errors.ts:630](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L630) +Defined in: [packages/db/src/errors.ts:645](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L645) #### Parameters diff --git a/docs/reference/classes/StorageKeyRequiredError.md b/docs/reference/classes/StorageKeyRequiredError.md index 43c7b92c1b..4dc6d658d9 100644 --- a/docs/reference/classes/StorageKeyRequiredError.md +++ b/docs/reference/classes/StorageKeyRequiredError.md @@ -5,7 +5,7 @@ title: StorageKeyRequiredError # Class: StorageKeyRequiredError -Defined in: [packages/db/src/errors.ts:652](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L652) +Defined in: [packages/db/src/errors.ts:667](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L667) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:652](https://github.com/TanStack/db/blob/ new StorageKeyRequiredError(): StorageKeyRequiredError; ``` -Defined in: [packages/db/src/errors.ts:653](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L653) +Defined in: [packages/db/src/errors.ts:668](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L668) #### Returns diff --git a/docs/reference/classes/SubscriptionNotFoundError.md b/docs/reference/classes/SubscriptionNotFoundError.md index 29f745156e..77d355a87f 100644 --- a/docs/reference/classes/SubscriptionNotFoundError.md +++ b/docs/reference/classes/SubscriptionNotFoundError.md @@ -5,7 +5,7 @@ title: SubscriptionNotFoundError # Class: SubscriptionNotFoundError -Defined in: [packages/db/src/errors.ts:714](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L714) +Defined in: [packages/db/src/errors.ts:729](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L729) Error when a subscription cannot be found during lazy join processing. For subqueries, aliases may be remapped (e.g., 'activeUser' → 'user'). @@ -26,7 +26,7 @@ new SubscriptionNotFoundError( availableAliases): SubscriptionNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:715](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L715) +Defined in: [packages/db/src/errors.ts:730](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L730) #### Parameters diff --git a/docs/reference/classes/SyncCleanupError.md b/docs/reference/classes/SyncCleanupError.md index f36d0e1d45..4ca87f307c 100644 --- a/docs/reference/classes/SyncCleanupError.md +++ b/docs/reference/classes/SyncCleanupError.md @@ -5,7 +5,7 @@ title: SyncCleanupError # Class: SyncCleanupError -Defined in: [packages/db/src/errors.ts:675](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L675) +Defined in: [packages/db/src/errors.ts:690](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L690) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:675](https://github.com/TanStack/db/blob/ new SyncCleanupError(collectionId, error): SyncCleanupError; ``` -Defined in: [packages/db/src/errors.ts:676](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L676) +Defined in: [packages/db/src/errors.ts:691](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L691) #### Parameters diff --git a/docs/reference/classes/UnknownExpressionTypeError.md b/docs/reference/classes/UnknownExpressionTypeError.md index 0aadc0abbc..bf1fca71ce 100644 --- a/docs/reference/classes/UnknownExpressionTypeError.md +++ b/docs/reference/classes/UnknownExpressionTypeError.md @@ -5,7 +5,7 @@ title: UnknownExpressionTypeError # Class: UnknownExpressionTypeError -Defined in: [packages/db/src/errors.ts:512](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L512) +Defined in: [packages/db/src/errors.ts:527](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L527) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:512](https://github.com/TanStack/db/blob/ new UnknownExpressionTypeError(type): UnknownExpressionTypeError; ``` -Defined in: [packages/db/src/errors.ts:513](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L513) +Defined in: [packages/db/src/errors.ts:528](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L528) #### Parameters diff --git a/docs/reference/classes/UnknownFunctionError.md b/docs/reference/classes/UnknownFunctionError.md index fda586a8fb..c513cd1ca4 100644 --- a/docs/reference/classes/UnknownFunctionError.md +++ b/docs/reference/classes/UnknownFunctionError.md @@ -5,7 +5,7 @@ title: UnknownFunctionError # Class: UnknownFunctionError -Defined in: [packages/db/src/errors.ts:524](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L524) +Defined in: [packages/db/src/errors.ts:539](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L539) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:524](https://github.com/TanStack/db/blob/ new UnknownFunctionError(functionName): UnknownFunctionError; ``` -Defined in: [packages/db/src/errors.ts:525](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L525) +Defined in: [packages/db/src/errors.ts:540](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L540) #### Parameters diff --git a/docs/reference/classes/UnknownHavingExpressionTypeError.md b/docs/reference/classes/UnknownHavingExpressionTypeError.md index 6f4806558a..df9f082b73 100644 --- a/docs/reference/classes/UnknownHavingExpressionTypeError.md +++ b/docs/reference/classes/UnknownHavingExpressionTypeError.md @@ -5,7 +5,7 @@ title: UnknownHavingExpressionTypeError # Class: UnknownHavingExpressionTypeError -Defined in: [packages/db/src/errors.ts:622](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L622) +Defined in: [packages/db/src/errors.ts:637](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L637) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:622](https://github.com/TanStack/db/blob/ new UnknownHavingExpressionTypeError(type): UnknownHavingExpressionTypeError; ``` -Defined in: [packages/db/src/errors.ts:623](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L623) +Defined in: [packages/db/src/errors.ts:638](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L638) #### Parameters diff --git a/docs/reference/classes/UnsupportedAggregateFunctionError.md b/docs/reference/classes/UnsupportedAggregateFunctionError.md index b2eaefe29b..1e55823861 100644 --- a/docs/reference/classes/UnsupportedAggregateFunctionError.md +++ b/docs/reference/classes/UnsupportedAggregateFunctionError.md @@ -5,7 +5,7 @@ title: UnsupportedAggregateFunctionError # Class: UnsupportedAggregateFunctionError -Defined in: [packages/db/src/errors.ts:608](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L608) +Defined in: [packages/db/src/errors.ts:623](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L623) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:608](https://github.com/TanStack/db/blob/ new UnsupportedAggregateFunctionError(functionName): UnsupportedAggregateFunctionError; ``` -Defined in: [packages/db/src/errors.ts:609](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L609) +Defined in: [packages/db/src/errors.ts:624](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L624) #### Parameters diff --git a/docs/reference/classes/UnsupportedFromTypeError.md b/docs/reference/classes/UnsupportedFromTypeError.md index a2a98b89da..1990e860c5 100644 --- a/docs/reference/classes/UnsupportedFromTypeError.md +++ b/docs/reference/classes/UnsupportedFromTypeError.md @@ -5,7 +5,7 @@ title: UnsupportedFromTypeError # Class: UnsupportedFromTypeError -Defined in: [packages/db/src/errors.ts:506](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L506) +Defined in: [packages/db/src/errors.ts:521](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L521) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:506](https://github.com/TanStack/db/blob/ new UnsupportedFromTypeError(type): UnsupportedFromTypeError; ``` -Defined in: [packages/db/src/errors.ts:507](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L507) +Defined in: [packages/db/src/errors.ts:522](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L522) #### Parameters diff --git a/docs/reference/classes/UnsupportedJoinSourceTypeError.md b/docs/reference/classes/UnsupportedJoinSourceTypeError.md index f355eee52c..2bb22f90ef 100644 --- a/docs/reference/classes/UnsupportedJoinSourceTypeError.md +++ b/docs/reference/classes/UnsupportedJoinSourceTypeError.md @@ -5,7 +5,7 @@ title: UnsupportedJoinSourceTypeError # Class: UnsupportedJoinSourceTypeError -Defined in: [packages/db/src/errors.ts:586](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L586) +Defined in: [packages/db/src/errors.ts:601](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L601) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:586](https://github.com/TanStack/db/blob/ new UnsupportedJoinSourceTypeError(type): UnsupportedJoinSourceTypeError; ``` -Defined in: [packages/db/src/errors.ts:587](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L587) +Defined in: [packages/db/src/errors.ts:602](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L602) #### Parameters diff --git a/docs/reference/classes/UnsupportedJoinTypeError.md b/docs/reference/classes/UnsupportedJoinTypeError.md index 23ba03b1b1..08587d8055 100644 --- a/docs/reference/classes/UnsupportedJoinTypeError.md +++ b/docs/reference/classes/UnsupportedJoinTypeError.md @@ -5,7 +5,7 @@ title: UnsupportedJoinTypeError # Class: UnsupportedJoinTypeError -Defined in: [packages/db/src/errors.ts:544](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L544) +Defined in: [packages/db/src/errors.ts:559](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L559) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:544](https://github.com/TanStack/db/blob/ new UnsupportedJoinTypeError(joinType): UnsupportedJoinTypeError; ``` -Defined in: [packages/db/src/errors.ts:545](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L545) +Defined in: [packages/db/src/errors.ts:560](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L560) #### Parameters diff --git a/docs/reference/classes/UnsupportedRootScalarSelectError.md b/docs/reference/classes/UnsupportedRootScalarSelectError.md index a3996fdf17..b2eabacd4e 100644 --- a/docs/reference/classes/UnsupportedRootScalarSelectError.md +++ b/docs/reference/classes/UnsupportedRootScalarSelectError.md @@ -5,7 +5,7 @@ title: UnsupportedRootScalarSelectError # Class: UnsupportedRootScalarSelectError -Defined in: [packages/db/src/errors.ts:447](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L447) +Defined in: [packages/db/src/errors.ts:462](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L462) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:447](https://github.com/TanStack/db/blob/ new UnsupportedRootScalarSelectError(): UnsupportedRootScalarSelectError; ``` -Defined in: [packages/db/src/errors.ts:448](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L448) +Defined in: [packages/db/src/errors.ts:463](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L463) #### Returns diff --git a/docs/reference/classes/WhereClauseConversionError.md b/docs/reference/classes/WhereClauseConversionError.md index 21d94f4594..a2f0bc2b53 100644 --- a/docs/reference/classes/WhereClauseConversionError.md +++ b/docs/reference/classes/WhereClauseConversionError.md @@ -5,7 +5,7 @@ title: WhereClauseConversionError # Class: WhereClauseConversionError -Defined in: [packages/db/src/errors.ts:702](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L702) +Defined in: [packages/db/src/errors.ts:717](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L717) Internal error when the query optimizer fails to convert a WHERE clause to a collection filter. @@ -21,7 +21,7 @@ Internal error when the query optimizer fails to convert a WHERE clause to a col new WhereClauseConversionError(collectionId, alias): WhereClauseConversionError; ``` -Defined in: [packages/db/src/errors.ts:703](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L703) +Defined in: [packages/db/src/errors.ts:718](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L718) #### Parameters diff --git a/docs/reference/electric-db-collection/functions/isChangeMessage.md b/docs/reference/electric-db-collection/functions/isChangeMessage.md index 0e9732cd2a..81de9bff39 100644 --- a/docs/reference/electric-db-collection/functions/isChangeMessage.md +++ b/docs/reference/electric-db-collection/functions/isChangeMessage.md @@ -9,7 +9,7 @@ title: isChangeMessage function isChangeMessage(message): message is ChangeMessage; ``` -Defined in: node\_modules/.pnpm/@electric-sql+client@1.5.14/node\_modules/@electric-sql/client/dist/index.d.ts:889 +Defined in: node\_modules/.pnpm/@electric-sql+client@1.5.15/node\_modules/@electric-sql/client/dist/index.d.ts:922 Type guard for checking Message is ChangeMessage. diff --git a/docs/reference/electric-db-collection/functions/isControlMessage.md b/docs/reference/electric-db-collection/functions/isControlMessage.md index 66ea4b70f3..6cd6b1582a 100644 --- a/docs/reference/electric-db-collection/functions/isControlMessage.md +++ b/docs/reference/electric-db-collection/functions/isControlMessage.md @@ -9,7 +9,7 @@ title: isControlMessage function isControlMessage(message): message is ControlMessage; ``` -Defined in: node\_modules/.pnpm/@electric-sql+client@1.5.14/node\_modules/@electric-sql/client/dist/index.d.ts:907 +Defined in: node\_modules/.pnpm/@electric-sql+client@1.5.15/node\_modules/@electric-sql/client/dist/index.d.ts:940 Type guard for checking Message is ControlMessage. diff --git a/docs/reference/functions/add.md b/docs/reference/functions/add.md index 1c3ceb9246..c7dddb77cf 100644 --- a/docs/reference/functions/add.md +++ b/docs/reference/functions/add.md @@ -6,10 +6,10 @@ title: add # Function: add() ```ts -function add(left, right): BinaryNumericReturnType; +function add(left, right): BinaryNumericReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:354](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L354) +Defined in: [packages/db/src/query/builder/functions.ts:601](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L601) ## Type Parameters @@ -33,4 +33,4 @@ Defined in: [packages/db/src/query/builder/functions.ts:354](https://github.com/ ## Returns -`BinaryNumericReturnType`\<`T1`, `T2`\> +`BinaryNumericReturnType` diff --git a/docs/reference/functions/and.md b/docs/reference/functions/and.md index b99e1c2326..b463b2ec2f 100644 --- a/docs/reference/functions/and.md +++ b/docs/reference/functions/and.md @@ -11,7 +11,7 @@ title: and function and(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:199](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L199) +Defined in: [packages/db/src/query/builder/functions.ts:203](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L203) ### Parameters @@ -36,7 +36,7 @@ function and( rest): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:203](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L203) +Defined in: [packages/db/src/query/builder/functions.ts:207](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L207) ### Parameters diff --git a/docs/reference/functions/avg.md b/docs/reference/functions/avg.md index acf474d673..2613e64655 100644 --- a/docs/reference/functions/avg.md +++ b/docs/reference/functions/avg.md @@ -9,7 +9,7 @@ title: avg function avg(arg): AggregateReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:370](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L370) +Defined in: [packages/db/src/query/builder/functions.ts:647](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L647) ## Type Parameters diff --git a/docs/reference/functions/caseWhen.md b/docs/reference/functions/caseWhen.md new file mode 100644 index 0000000000..17559636a0 --- /dev/null +++ b/docs/reference/functions/caseWhen.md @@ -0,0 +1,1299 @@ +--- +id: caseWhen +title: caseWhen +--- + +# Function: caseWhen() + +## Call Signature + +```ts +function caseWhen(condition1, value1): CaseWhenResult<[V1], false>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:399](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L399) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +### Returns + +`CaseWhenResult`\<\[`V1`\], `false`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, +defaultValue): CaseWhenResult<[V1, D], true>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:403](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L403) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### D + +`D` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### defaultValue + +`D` + +### Returns + +`CaseWhenResult`\<\[`V1`, `D`\], `true`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, +value2): CaseWhenResult<[V1, V2], false>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:408](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L408) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`\], `false`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, +defaultValue): CaseWhenResult<[V1, V2, D], true>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:419](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L419) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### D + +`D` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### defaultValue + +`D` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`, `D`\], `true`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, + condition3, +value3): CaseWhenResult<[V1, V2, V3], false>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:432](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L432) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### C3 + +`C3` *extends* `ExpressionLike` + +#### V3 + +`V3` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### condition3 + +`C3` + +#### value3 + +`V3` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`, `V3`\], `false`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, + condition3, + value3, +defaultValue): CaseWhenResult<[V1, V2, V3, D], true>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:447](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L447) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### C3 + +`C3` *extends* `ExpressionLike` + +#### V3 + +`V3` *extends* `CaseWhenValue` + +#### D + +`D` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### condition3 + +`C3` + +#### value3 + +`V3` + +#### defaultValue + +`D` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`, `V3`, `D`\], `true`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, + condition3, + value3, + condition4, +value4): CaseWhenResult<[V1, V2, V3, V4], false>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:464](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L464) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### C3 + +`C3` *extends* `ExpressionLike` + +#### V3 + +`V3` *extends* `CaseWhenValue` + +#### C4 + +`C4` *extends* `ExpressionLike` + +#### V4 + +`V4` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### condition3 + +`C3` + +#### value3 + +`V3` + +#### condition4 + +`C4` + +#### value4 + +`V4` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`, `V3`, `V4`\], `false`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, + condition3, + value3, + condition4, + value4, +defaultValue): CaseWhenResult<[V1, V2, V3, V4, D], true>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:483](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L483) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### C3 + +`C3` *extends* `ExpressionLike` + +#### V3 + +`V3` *extends* `CaseWhenValue` + +#### C4 + +`C4` *extends* `ExpressionLike` + +#### V4 + +`V4` *extends* `CaseWhenValue` + +#### D + +`D` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### condition3 + +`C3` + +#### value3 + +`V3` + +#### condition4 + +`C4` + +#### value4 + +`V4` + +#### defaultValue + +`D` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`, `V3`, `V4`, `D`\], `true`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, + condition3, + value3, + condition4, + value4, + condition5, +value5): CaseWhenResult<[V1, V2, V3, V4, V5], false>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:504](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L504) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### C3 + +`C3` *extends* `ExpressionLike` + +#### V3 + +`V3` *extends* `CaseWhenValue` + +#### C4 + +`C4` *extends* `ExpressionLike` + +#### V4 + +`V4` *extends* `CaseWhenValue` + +#### C5 + +`C5` *extends* `ExpressionLike` + +#### V5 + +`V5` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### condition3 + +`C3` + +#### value3 + +`V3` + +#### condition4 + +`C4` + +#### value4 + +`V4` + +#### condition5 + +`C5` + +#### value5 + +`V5` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`, `V3`, `V4`, `V5`\], `false`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, + condition3, + value3, + condition4, + value4, + condition5, + value5, +defaultValue): CaseWhenResult<[V1, V2, V3, V4, V5, D], true>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:527](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L527) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### C3 + +`C3` *extends* `ExpressionLike` + +#### V3 + +`V3` *extends* `CaseWhenValue` + +#### C4 + +`C4` *extends* `ExpressionLike` + +#### V4 + +`V4` *extends* `CaseWhenValue` + +#### C5 + +`C5` *extends* `ExpressionLike` + +#### V5 + +`V5` *extends* `CaseWhenValue` + +#### D + +`D` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### condition3 + +`C3` + +#### value3 + +`V3` + +#### condition4 + +`C4` + +#### value4 + +`V4` + +#### condition5 + +`C5` + +#### value5 + +`V5` + +#### defaultValue + +`D` + +### Returns + +`CaseWhenResult`\<\[`V1`, `V2`, `V3`, `V4`, `V5`, `D`\], `true`\> + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` + +## Call Signature + +```ts +function caseWhen( + condition1, + value1, + condition2, + value2, + condition3, + value3, + condition4, + value4, + condition5, + value5, + condition6, + value6, ... + rest): any; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:552](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L552) + +Returns the value for the first matching condition, similar to SQL +`CASE WHEN`. + +Arguments are evaluated as condition/value pairs followed by an optional +default value. Scalar branch values return a query expression and can be used +in expression contexts like `select`, `where`, `orderBy`, `groupBy`, +`having`, and equality join operands. If no scalar branch matches and no +default is provided, the result is `null`. + +When a branch value is a projection object, `caseWhen` becomes a select-only +projection value. Projection branches can include nested fields, ref spreads, +and includes. If no projection branch matches and no default is provided, the +result is `undefined`. + +### Type Parameters + +#### C1 + +`C1` *extends* `ExpressionLike` + +#### V1 + +`V1` *extends* `CaseWhenValue` + +#### C2 + +`C2` *extends* `ExpressionLike` + +#### V2 + +`V2` *extends* `CaseWhenValue` + +#### C3 + +`C3` *extends* `ExpressionLike` + +#### V3 + +`V3` *extends* `CaseWhenValue` + +#### C4 + +`C4` *extends* `ExpressionLike` + +#### V4 + +`V4` *extends* `CaseWhenValue` + +#### C5 + +`C5` *extends* `ExpressionLike` + +#### V5 + +`V5` *extends* `CaseWhenValue` + +### Parameters + +#### condition1 + +`C1` + +#### value1 + +`V1` + +#### condition2 + +`C2` + +#### value2 + +`V2` + +#### condition3 + +`C3` + +#### value3 + +`V3` + +#### condition4 + +`C4` + +#### value4 + +`V4` + +#### condition5 + +`C5` + +#### value5 + +`V5` + +#### condition6 + +`ExpressionLike` + +#### value6 + +`CaseWhenValue` + +#### rest + +...`CaseWhenValue`[] + +### Returns + +`any` + +### Examples + +```ts +caseWhen(gt(user.age, 18), `adult`, `minor`) +``` + +```ts +caseWhen( + gt(user.age, 65), + `senior`, + gt(user.age, 18), + `adult`, + `minor`, +) +``` + +```ts +caseWhen(gt(user.age, 18), { + ...user, + posts: q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), +}) +``` diff --git a/docs/reference/functions/coalesce.md b/docs/reference/functions/coalesce.md index d78f0bbc8c..6d4e95ecb2 100644 --- a/docs/reference/functions/coalesce.md +++ b/docs/reference/functions/coalesce.md @@ -9,7 +9,7 @@ title: coalesce function coalesce(...args): CoalesceReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:345](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L345) +Defined in: [packages/db/src/query/builder/functions.ts:349](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L349) ## Type Parameters diff --git a/docs/reference/functions/compileQuery.md b/docs/reference/functions/compileQuery.md index 4f6db2f575..049b05d593 100644 --- a/docs/reference/functions/compileQuery.md +++ b/docs/reference/functions/compileQuery.md @@ -21,7 +21,7 @@ function compileQuery( childCorrelationField?): CompilationResult; ``` -Defined in: [packages/db/src/query/compiler/index.ts:130](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/index.ts#L130) +Defined in: [packages/db/src/query/compiler/index.ts:162](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/index.ts#L162) Compiles a query IR into a D2 pipeline diff --git a/docs/reference/functions/concat.md b/docs/reference/functions/concat.md index ddfaf560e5..8ac8228207 100644 --- a/docs/reference/functions/concat.md +++ b/docs/reference/functions/concat.md @@ -11,7 +11,7 @@ title: concat function concat(arg): ConcatToArrayWrapper; ``` -Defined in: [packages/db/src/query/builder/functions.ts:297](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L297) +Defined in: [packages/db/src/query/builder/functions.ts:301](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L301) ### Type Parameters @@ -35,7 +35,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:297](https://github.com/ function concat(...args): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:300](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L300) +Defined in: [packages/db/src/query/builder/functions.ts:304](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L304) ### Parameters diff --git a/docs/reference/functions/count.md b/docs/reference/functions/count.md index c1a8436e97..f09ebb4134 100644 --- a/docs/reference/functions/count.md +++ b/docs/reference/functions/count.md @@ -9,7 +9,7 @@ title: count function count(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:366](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L366) +Defined in: [packages/db/src/query/builder/functions.ts:643](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L643) ## Parameters diff --git a/docs/reference/functions/createCollection.md b/docs/reference/functions/createCollection.md index c36372d477..69d77e7a10 100644 --- a/docs/reference/functions/createCollection.md +++ b/docs/reference/functions/createCollection.md @@ -11,7 +11,7 @@ title: createCollection function createCollection(options): Collection, TKey, TUtils, T, InferSchemaInput> & NonSingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:140](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L140) +Defined in: [packages/db/src/collection/index.ts:141](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L141) Creates a new Collection instance with the given configuration @@ -120,7 +120,7 @@ const todos = createCollection({ function createCollection(options): Collection, TKey, Exclude, T, InferSchemaInput> & NonSingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L157) +Defined in: [packages/db/src/collection/index.ts:158](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L158) Creates a new Collection instance with the given configuration @@ -229,7 +229,7 @@ const todos = createCollection({ function createCollection(options): Collection, TKey, TUtils, T, InferSchemaInput> & SingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:175](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L175) +Defined in: [packages/db/src/collection/index.ts:176](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L176) Creates a new Collection instance with the given configuration @@ -338,7 +338,7 @@ const todos = createCollection({ function createCollection(options): Collection, TKey, TUtils, T, InferSchemaInput> & SingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:191](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L191) +Defined in: [packages/db/src/collection/index.ts:192](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L192) Creates a new Collection instance with the given configuration @@ -447,7 +447,7 @@ const todos = createCollection({ function createCollection(options): Collection & NonSingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:204](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L204) +Defined in: [packages/db/src/collection/index.ts:205](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L205) Creates a new Collection instance with the given configuration @@ -556,7 +556,7 @@ const todos = createCollection({ function createCollection(options): Collection & NonSingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:217](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L217) +Defined in: [packages/db/src/collection/index.ts:218](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L218) Creates a new Collection instance with the given configuration @@ -665,7 +665,7 @@ const todos = createCollection({ function createCollection(options): Collection & SingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:229](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L229) +Defined in: [packages/db/src/collection/index.ts:230](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L230) Creates a new Collection instance with the given configuration @@ -774,7 +774,7 @@ const todos = createCollection({ function createCollection(options): Collection & SingleResult; ``` -Defined in: [packages/db/src/collection/index.ts:242](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L242) +Defined in: [packages/db/src/collection/index.ts:243](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L243) Creates a new Collection instance with the given configuration diff --git a/docs/reference/functions/createTransaction.md b/docs/reference/functions/createTransaction.md index e1207af829..596bd96331 100644 --- a/docs/reference/functions/createTransaction.md +++ b/docs/reference/functions/createTransaction.md @@ -9,7 +9,7 @@ title: createTransaction function createTransaction(config): Transaction; ``` -Defined in: [packages/db/src/transactions.ts:156](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L156) +Defined in: [packages/db/src/transactions.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L157) Creates a new transaction for grouping multiple collection operations diff --git a/docs/reference/functions/divide.md b/docs/reference/functions/divide.md new file mode 100644 index 0000000000..b8424ec323 --- /dev/null +++ b/docs/reference/functions/divide.md @@ -0,0 +1,36 @@ +--- +id: divide +title: divide +--- + +# Function: divide() + +```ts +function divide(left, right): DivideReturnType; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:631](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L631) + +## Type Parameters + +### T1 + +`T1` *extends* `ExpressionLike` + +### T2 + +`T2` *extends* `ExpressionLike` + +## Parameters + +### left + +`T1` + +### right + +`T2` + +## Returns + +`DivideReturnType` diff --git a/docs/reference/functions/eq.md b/docs/reference/functions/eq.md index 794ec946c3..bda6e68399 100644 --- a/docs/reference/functions/eq.md +++ b/docs/reference/functions/eq.md @@ -11,7 +11,7 @@ title: eq function eq(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:133](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L133) +Defined in: [packages/db/src/query/builder/functions.ts:137](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L137) ### Type Parameters @@ -39,7 +39,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:133](https://github.com/ function eq(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:137](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L137) +Defined in: [packages/db/src/query/builder/functions.ts:141](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L141) ### Type Parameters @@ -67,7 +67,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:137](https://github.com/ function eq(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:141](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L141) +Defined in: [packages/db/src/query/builder/functions.ts:145](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L145) ### Type Parameters diff --git a/docs/reference/functions/getActiveTransaction.md b/docs/reference/functions/getActiveTransaction.md index b1488c8d08..45a5bd2514 100644 --- a/docs/reference/functions/getActiveTransaction.md +++ b/docs/reference/functions/getActiveTransaction.md @@ -11,7 +11,7 @@ function getActiveTransaction(): | undefined; ``` -Defined in: [packages/db/src/transactions.ts:175](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L175) +Defined in: [packages/db/src/transactions.ts:176](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L176) Gets the currently active ambient transaction, if any Used internally by collection operations to join existing transactions diff --git a/docs/reference/functions/gt.md b/docs/reference/functions/gt.md index 42f0ff1883..5e7dd5cc89 100644 --- a/docs/reference/functions/gt.md +++ b/docs/reference/functions/gt.md @@ -11,7 +11,7 @@ title: gt function gt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:146](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L146) +Defined in: [packages/db/src/query/builder/functions.ts:150](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L150) ### Type Parameters @@ -39,7 +39,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:146](https://github.com/ function gt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:150](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L150) +Defined in: [packages/db/src/query/builder/functions.ts:154](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L154) ### Type Parameters @@ -67,7 +67,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:150](https://github.com/ function gt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:154](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L154) +Defined in: [packages/db/src/query/builder/functions.ts:158](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L158) ### Type Parameters diff --git a/docs/reference/functions/gte.md b/docs/reference/functions/gte.md index 18aecd8c67..efcba19155 100644 --- a/docs/reference/functions/gte.md +++ b/docs/reference/functions/gte.md @@ -11,7 +11,7 @@ title: gte function gte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:159](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L159) +Defined in: [packages/db/src/query/builder/functions.ts:163](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L163) ### Type Parameters @@ -39,7 +39,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:159](https://github.com/ function gte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:163](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L163) +Defined in: [packages/db/src/query/builder/functions.ts:167](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L167) ### Type Parameters @@ -67,7 +67,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:163](https://github.com/ function gte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:167](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L167) +Defined in: [packages/db/src/query/builder/functions.ts:171](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L171) ### Type Parameters diff --git a/docs/reference/functions/ilike.md b/docs/reference/functions/ilike.md index faf4f82371..4d51513275 100644 --- a/docs/reference/functions/ilike.md +++ b/docs/reference/functions/ilike.md @@ -9,7 +9,7 @@ title: ilike function ilike(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:270](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L270) +Defined in: [packages/db/src/query/builder/functions.ts:274](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L274) ## Parameters diff --git a/docs/reference/functions/inArray.md b/docs/reference/functions/inArray.md index 6a8d545296..c6ed1471b9 100644 --- a/docs/reference/functions/inArray.md +++ b/docs/reference/functions/inArray.md @@ -9,7 +9,7 @@ title: inArray function inArray(value, array): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:255](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L255) +Defined in: [packages/db/src/query/builder/functions.ts:259](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L259) ## Parameters diff --git a/docs/reference/functions/isNull.md b/docs/reference/functions/isNull.md index cf303fc635..fbd75223ca 100644 --- a/docs/reference/functions/isNull.md +++ b/docs/reference/functions/isNull.md @@ -9,7 +9,7 @@ title: isNull function isNull(value): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:251](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L251) +Defined in: [packages/db/src/query/builder/functions.ts:255](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L255) ## Parameters diff --git a/docs/reference/functions/isUndefined.md b/docs/reference/functions/isUndefined.md index 49d99d535b..5444e21830 100644 --- a/docs/reference/functions/isUndefined.md +++ b/docs/reference/functions/isUndefined.md @@ -9,7 +9,7 @@ title: isUndefined function isUndefined(value): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:247](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L247) +Defined in: [packages/db/src/query/builder/functions.ts:251](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L251) ## Parameters diff --git a/docs/reference/functions/length.md b/docs/reference/functions/length.md index d452d3a863..1586545934 100644 --- a/docs/reference/functions/length.md +++ b/docs/reference/functions/length.md @@ -9,7 +9,7 @@ title: length function length(arg): NumericFunctionReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:291](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L291) +Defined in: [packages/db/src/query/builder/functions.ts:295](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L295) ## Type Parameters diff --git a/docs/reference/functions/like.md b/docs/reference/functions/like.md index a05fa13226..ee6f72f03e 100644 --- a/docs/reference/functions/like.md +++ b/docs/reference/functions/like.md @@ -9,7 +9,7 @@ title: like function like(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:262](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L262) +Defined in: [packages/db/src/query/builder/functions.ts:266](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L266) ## Parameters diff --git a/docs/reference/functions/localOnlyCollectionOptions.md b/docs/reference/functions/localOnlyCollectionOptions.md index 41c22862b3..3f034ae8d3 100644 --- a/docs/reference/functions/localOnlyCollectionOptions.md +++ b/docs/reference/functions/localOnlyCollectionOptions.md @@ -11,7 +11,7 @@ title: localOnlyCollectionOptions function localOnlyCollectionOptions(config): CollectionConfig, TKey, T, LocalOnlyCollectionUtils> & object & object; ``` -Defined in: [packages/db/src/local-only.ts:149](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L149) +Defined in: [packages/db/src/local-only.ts:150](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L150) Creates Local-only collection options for use with a standard Collection @@ -123,7 +123,7 @@ await tx.commit() function localOnlyCollectionOptions(config): CollectionConfig & object & object; ``` -Defined in: [packages/db/src/local-only.ts:162](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L162) +Defined in: [packages/db/src/local-only.ts:163](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L163) Creates Local-only collection options for use with a standard Collection diff --git a/docs/reference/functions/localStorageCollectionOptions.md b/docs/reference/functions/localStorageCollectionOptions.md index 6ea5ecad08..4516ac84a8 100644 --- a/docs/reference/functions/localStorageCollectionOptions.md +++ b/docs/reference/functions/localStorageCollectionOptions.md @@ -11,7 +11,7 @@ title: localStorageCollectionOptions function localStorageCollectionOptions(config): CollectionConfig, TKey, T, LocalStorageCollectionUtils> & object; ``` -Defined in: [packages/db/src/local-storage.ts:316](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L316) +Defined in: [packages/db/src/local-storage.ts:317](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L317) Creates localStorage collection options for use with a standard Collection @@ -126,7 +126,7 @@ await tx.commit() function localStorageCollectionOptions(config): CollectionConfig & object; ``` -Defined in: [packages/db/src/local-storage.ts:336](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L336) +Defined in: [packages/db/src/local-storage.ts:337](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L337) Creates localStorage collection options for use with a standard Collection diff --git a/docs/reference/functions/lower.md b/docs/reference/functions/lower.md index fff4fc0830..f9739693e7 100644 --- a/docs/reference/functions/lower.md +++ b/docs/reference/functions/lower.md @@ -9,7 +9,7 @@ title: lower function lower(arg): StringFunctionReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:285](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L285) +Defined in: [packages/db/src/query/builder/functions.ts:289](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L289) ## Type Parameters diff --git a/docs/reference/functions/lt.md b/docs/reference/functions/lt.md index 7c204f4a93..e103058e7d 100644 --- a/docs/reference/functions/lt.md +++ b/docs/reference/functions/lt.md @@ -11,7 +11,7 @@ title: lt function lt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:172](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L172) +Defined in: [packages/db/src/query/builder/functions.ts:176](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L176) ### Type Parameters @@ -39,7 +39,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:172](https://github.com/ function lt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:176](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L176) +Defined in: [packages/db/src/query/builder/functions.ts:180](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L180) ### Type Parameters @@ -67,7 +67,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:176](https://github.com/ function lt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:180](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L180) +Defined in: [packages/db/src/query/builder/functions.ts:184](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L184) ### Type Parameters diff --git a/docs/reference/functions/lte.md b/docs/reference/functions/lte.md index 2b2b5423cc..01fc872a0e 100644 --- a/docs/reference/functions/lte.md +++ b/docs/reference/functions/lte.md @@ -11,7 +11,7 @@ title: lte function lte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:185](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L185) +Defined in: [packages/db/src/query/builder/functions.ts:189](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L189) ### Type Parameters @@ -39,7 +39,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:185](https://github.com/ function lte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:189](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L189) +Defined in: [packages/db/src/query/builder/functions.ts:193](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L193) ### Type Parameters @@ -67,7 +67,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:189](https://github.com/ function lte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:193](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L193) +Defined in: [packages/db/src/query/builder/functions.ts:197](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L197) ### Type Parameters diff --git a/docs/reference/functions/materialize.md b/docs/reference/functions/materialize.md new file mode 100644 index 0000000000..8f80a39eac --- /dev/null +++ b/docs/reference/functions/materialize.md @@ -0,0 +1,58 @@ +--- +id: materialize +title: materialize +--- + +# Function: materialize() + +```ts +function materialize(query): MaterializeWrapper, TContext extends SingleResult ? true : false>; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:848](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L848) + +Materialize an includes subquery into a plain value on the parent row. + +- For multi-row subqueries, the parent receives an `Array` snapshot + (equivalent to `toArray()`). +- For `findOne()` subqueries, the parent receives a single `T | undefined` + value — `undefined` when no child matches. + +The snapshot updates reactively: parent rows re-emit when the underlying +children change. + +## Type Parameters + +### TContext + +`TContext` *extends* [`Context`](../interfaces/Context.md) + +## Parameters + +### query + +[`QueryBuilder`](../type-aliases/QueryBuilder.md)\<`TContext`\> + +## Returns + +`MaterializeWrapper`\<`GetRawResult`\<`TContext`\>, `TContext` *extends* [`SingleResult`](../type-aliases/SingleResult.md) ? `true` : `false`\> + +## Example + +```ts +// Multi-row: produces Array on each project +select(({ p }) => ({ + ...p, + issues: materialize( + q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + ), +})) + +// Singleton: produces Author | undefined on each post +select(({ p }) => ({ + ...p, + author: materialize( + q.from({ a: authors }).where(({ a }) => eq(a.id, p.authorId)).findOne(), + ), +})) +``` diff --git a/docs/reference/functions/max.md b/docs/reference/functions/max.md index 008439a7d1..6d04ccf084 100644 --- a/docs/reference/functions/max.md +++ b/docs/reference/functions/max.md @@ -9,7 +9,7 @@ title: max function max(arg): AggregateReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:382](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L382) +Defined in: [packages/db/src/query/builder/functions.ts:659](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L659) ## Type Parameters diff --git a/docs/reference/functions/min.md b/docs/reference/functions/min.md index 8c5788295b..f470eef662 100644 --- a/docs/reference/functions/min.md +++ b/docs/reference/functions/min.md @@ -9,7 +9,7 @@ title: min function min(arg): AggregateReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:378](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L378) +Defined in: [packages/db/src/query/builder/functions.ts:655](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L655) ## Type Parameters diff --git a/docs/reference/functions/multiply.md b/docs/reference/functions/multiply.md new file mode 100644 index 0000000000..868c04b5da --- /dev/null +++ b/docs/reference/functions/multiply.md @@ -0,0 +1,36 @@ +--- +id: multiply +title: multiply +--- + +# Function: multiply() + +```ts +function multiply(left, right): BinaryNumericReturnType; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:621](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L621) + +## Type Parameters + +### T1 + +`T1` *extends* `ExpressionLike` + +### T2 + +`T2` *extends* `ExpressionLike` + +## Parameters + +### left + +`T1` + +### right + +`T2` + +## Returns + +`BinaryNumericReturnType` diff --git a/docs/reference/functions/not.md b/docs/reference/functions/not.md index e6020ac2de..9b0c6b2164 100644 --- a/docs/reference/functions/not.md +++ b/docs/reference/functions/not.md @@ -9,7 +9,7 @@ title: not function not(value): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:242](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L242) +Defined in: [packages/db/src/query/builder/functions.ts:246](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L246) ## Parameters diff --git a/docs/reference/functions/or.md b/docs/reference/functions/or.md index 95adf2ff2e..73b058e122 100644 --- a/docs/reference/functions/or.md +++ b/docs/reference/functions/or.md @@ -11,7 +11,7 @@ title: or function or(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:221](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L221) +Defined in: [packages/db/src/query/builder/functions.ts:225](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L225) ### Parameters @@ -36,7 +36,7 @@ function or( rest): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:225](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L225) +Defined in: [packages/db/src/query/builder/functions.ts:229](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L229) ### Parameters diff --git a/docs/reference/functions/safeRandomUUID.md b/docs/reference/functions/safeRandomUUID.md new file mode 100644 index 0000000000..145a79caaf --- /dev/null +++ b/docs/reference/functions/safeRandomUUID.md @@ -0,0 +1,25 @@ +--- +id: safeRandomUUID +title: safeRandomUUID +--- + +# Function: safeRandomUUID() + +```ts +function safeRandomUUID(): string; +``` + +Defined in: [packages/db/src/utils/uuid.ts:11](https://github.com/TanStack/db/blob/main/packages/db/src/utils/uuid.ts#L11) + +Returns a RFC 4122 version 4 UUID. + +Prefers `crypto.randomUUID()` when available. In non-secure browser contexts +(e.g. a dev server accessed via a LAN IP over HTTP) `crypto.randomUUID` is +`undefined`, so this falls back to building a UUIDv4 from +`crypto.getRandomValues`. Throws if neither API is available. + +See https://github.com/TanStack/db/issues/1541. + +## Returns + +`string` diff --git a/docs/reference/functions/subtract.md b/docs/reference/functions/subtract.md new file mode 100644 index 0000000000..3ae204d17e --- /dev/null +++ b/docs/reference/functions/subtract.md @@ -0,0 +1,36 @@ +--- +id: subtract +title: subtract +--- + +# Function: subtract() + +```ts +function subtract(left, right): BinaryNumericReturnType; +``` + +Defined in: [packages/db/src/query/builder/functions.ts:611](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L611) + +## Type Parameters + +### T1 + +`T1` *extends* `ExpressionLike` + +### T2 + +`T2` *extends* `ExpressionLike` + +## Parameters + +### left + +`T1` + +### right + +`T2` + +## Returns + +`BinaryNumericReturnType` diff --git a/docs/reference/functions/sum.md b/docs/reference/functions/sum.md index 3dcf0b62d7..2b6850a87c 100644 --- a/docs/reference/functions/sum.md +++ b/docs/reference/functions/sum.md @@ -9,7 +9,7 @@ title: sum function sum(arg): AggregateReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:374](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L374) +Defined in: [packages/db/src/query/builder/functions.ts:651](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L651) ## Type Parameters diff --git a/docs/reference/functions/toArray.md b/docs/reference/functions/toArray.md index edcf651bf0..f4dbfcf302 100644 --- a/docs/reference/functions/toArray.md +++ b/docs/reference/functions/toArray.md @@ -9,7 +9,7 @@ title: toArray function toArray(query): ToArrayWrapper>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:453](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L453) +Defined in: [packages/db/src/query/builder/functions.ts:752](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L752) ## Type Parameters diff --git a/docs/reference/functions/upper.md b/docs/reference/functions/upper.md index 390fc9fb31..bb33cb7976 100644 --- a/docs/reference/functions/upper.md +++ b/docs/reference/functions/upper.md @@ -9,7 +9,7 @@ title: upper function upper(arg): StringFunctionReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:279](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L279) +Defined in: [packages/db/src/query/builder/functions.ts:283](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L283) ## Type Parameters diff --git a/docs/reference/index.md b/docs/reference/index.md index ce09d71916..f7d66fcba5 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -172,6 +172,9 @@ title: "@tanstack/db" - [ClearStorageFn](type-aliases/ClearStorageFn.md) - [CollectionConfigSingleRowOption](type-aliases/CollectionConfigSingleRowOption.md) - [CollectionStatus](type-aliases/CollectionStatus.md) +- [ContextFromSource](type-aliases/ContextFromSource.md) +- [ContextFromUnionBranches](type-aliases/ContextFromUnionBranches.md) +- [ContextFromUnionSource](type-aliases/ContextFromUnionSource.md) - [ContextSchema](type-aliases/ContextSchema.md) - [CursorExpressions](type-aliases/CursorExpressions.md) - [DeleteKeyMessage](type-aliases/DeleteKeyMessage.md) @@ -230,7 +233,9 @@ title: "@tanstack/db" - [SchemaFromSource](type-aliases/SchemaFromSource.md) - [SelectObject](type-aliases/SelectObject.md) - [SingleResult](type-aliases/SingleResult.md) +- [SingleSource](type-aliases/SingleSource.md) - [Source](type-aliases/Source.md) +- [SourceClauseContext](type-aliases/SourceClauseContext.md) - [StandardSchema](type-aliases/StandardSchema.md) - [StandardSchemaAlias](type-aliases/StandardSchemaAlias.md) - [StorageApi](type-aliases/StorageApi.md) @@ -284,6 +289,7 @@ title: "@tanstack/db" - [createTransaction](functions/createTransaction.md) - [debounceStrategy](functions/debounceStrategy.md) - [deepEquals](functions/deepEquals.md) +- [divide](functions/divide.md) - [eq](functions/eq.md) - [extractFieldPath](functions/extractFieldPath.md) - [extractSimpleComparisons](functions/extractSimpleComparisons.md) @@ -313,9 +319,11 @@ title: "@tanstack/db" - [lower](functions/lower.md) - [lt](functions/lt.md) - [lte](functions/lte.md) +- [materialize](functions/materialize.md) - [max](functions/max.md) - [min](functions/min.md) - [minusWherePredicates](functions/minusWherePredicates.md) +- [multiply](functions/multiply.md) - [not](functions/not.md) - [optimizeExpressionWithIndexes](functions/optimizeExpressionWithIndexes.md) - [or](functions/or.md) @@ -324,6 +332,8 @@ title: "@tanstack/db" - [parseWhereExpression](functions/parseWhereExpression.md) - [queryOnce](functions/queryOnce.md) - [queueStrategy](functions/queueStrategy.md) +- [safeRandomUUID](functions/safeRandomUUID.md) +- [subtract](functions/subtract.md) - [sum](functions/sum.md) - [throttleStrategy](functions/throttleStrategy.md) - [toArray](functions/toArray.md) diff --git a/docs/reference/interfaces/Collection.md b/docs/reference/interfaces/Collection.md index dfc0e6aab8..ce124438c4 100644 --- a/docs/reference/interfaces/Collection.md +++ b/docs/reference/interfaces/Collection.md @@ -5,7 +5,7 @@ title: Collection # Interface: Collection\ -Defined in: [packages/db/src/collection/index.ts:54](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L54) +Defined in: [packages/db/src/collection/index.ts:55](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L55) Enhanced Collection interface that includes both data type T and utilities TUtils @@ -51,7 +51,7 @@ The type for insert operations (can be different from T for schemas with default _lifecycle: CollectionLifecycleManager; ``` -Defined in: [packages/db/src/collection/index.ts:289](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L289) +Defined in: [packages/db/src/collection/index.ts:290](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L290) #### Inherited from @@ -65,7 +65,7 @@ Defined in: [packages/db/src/collection/index.ts:289](https://github.com/TanStac _state: CollectionStateManager; ``` -Defined in: [packages/db/src/collection/index.ts:301](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L301) +Defined in: [packages/db/src/collection/index.ts:302](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L302) #### Inherited from @@ -79,7 +79,7 @@ Defined in: [packages/db/src/collection/index.ts:301](https://github.com/TanStac _sync: CollectionSyncManager; ``` -Defined in: [packages/db/src/collection/index.ts:290](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L290) +Defined in: [packages/db/src/collection/index.ts:291](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L291) #### Inherited from @@ -93,7 +93,7 @@ Defined in: [packages/db/src/collection/index.ts:290](https://github.com/TanStac config: CollectionConfig; ``` -Defined in: [packages/db/src/collection/index.ts:280](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L280) +Defined in: [packages/db/src/collection/index.ts:281](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L281) #### Inherited from @@ -107,7 +107,7 @@ Defined in: [packages/db/src/collection/index.ts:280](https://github.com/TanStac deferDataRefresh: Promise | null = null; ``` -Defined in: [packages/db/src/collection/index.ts:308](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L308) +Defined in: [packages/db/src/collection/index.ts:309](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L309) When set, collection consumers should defer processing incoming data refreshes until this promise resolves. This prevents stale data from @@ -125,7 +125,7 @@ overwriting optimistic state while pending writes are being applied. id: string; ``` -Defined in: [packages/db/src/collection/index.ts:279](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L279) +Defined in: [packages/db/src/collection/index.ts:280](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L280) #### Inherited from @@ -139,7 +139,7 @@ Defined in: [packages/db/src/collection/index.ts:279](https://github.com/TanStac readonly optional singleResult: true; ``` -Defined in: [packages/db/src/collection/index.ts:62](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L62) +Defined in: [packages/db/src/collection/index.ts:63](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L63) *** @@ -149,7 +149,7 @@ Defined in: [packages/db/src/collection/index.ts:62](https://github.com/TanStack readonly utils: TUtils; ``` -Defined in: [packages/db/src/collection/index.ts:61](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L61) +Defined in: [packages/db/src/collection/index.ts:62](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L62) #### Overrides @@ -165,7 +165,7 @@ Defined in: [packages/db/src/collection/index.ts:61](https://github.com/TanStack get compareOptions(): StringCollationConfig; ``` -Defined in: [packages/db/src/collection/index.ts:643](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L643) +Defined in: [packages/db/src/collection/index.ts:644](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L644) ##### Returns @@ -185,7 +185,7 @@ Defined in: [packages/db/src/collection/index.ts:643](https://github.com/TanStac get indexes(): Map>; ``` -Defined in: [packages/db/src/collection/index.ts:628](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L628) +Defined in: [packages/db/src/collection/index.ts:629](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L629) Get resolved indexes for query optimization @@ -207,7 +207,7 @@ Get resolved indexes for query optimization get isLoadingSubset(): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:456](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L456) +Defined in: [packages/db/src/collection/index.ts:457](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L457) Check if the collection is currently loading more data @@ -231,7 +231,7 @@ true if the collection has pending load more operations, false otherwise get size(): number; ``` -Defined in: [packages/db/src/collection/index.ts:493](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L493) +Defined in: [packages/db/src/collection/index.ts:494](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L494) Get the current size of the collection (cached) @@ -253,7 +253,7 @@ Get the current size of the collection (cached) get state(): Map>; ``` -Defined in: [packages/db/src/collection/index.ts:820](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L820) +Defined in: [packages/db/src/collection/index.ts:821](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L821) Gets the current state of the collection as a Map @@ -293,7 +293,7 @@ Map containing all items in the collection, with keys as identifiers get status(): CollectionStatus; ``` -Defined in: [packages/db/src/collection/index.ts:411](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L411) +Defined in: [packages/db/src/collection/index.ts:412](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L412) Gets the current status of the collection @@ -315,7 +315,7 @@ Gets the current status of the collection get subscriberCount(): number; ``` -Defined in: [packages/db/src/collection/index.ts:418](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L418) +Defined in: [packages/db/src/collection/index.ts:419](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L419) Get the number of subscribers to the collection @@ -337,7 +337,7 @@ Get the number of subscribers to the collection get toArray(): WithVirtualProps[]; ``` -Defined in: [packages/db/src/collection/index.ts:849](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L849) +Defined in: [packages/db/src/collection/index.ts:850](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L850) Gets the current state of the collection as an Array @@ -359,7 +359,7 @@ An Array containing all items in the collection iterator: IterableIterator<[TKey, WithVirtualProps]>; ``` -Defined in: [packages/db/src/collection/index.ts:531](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L531) +Defined in: [packages/db/src/collection/index.ts:532](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L532) Get all entries (virtual derived state) @@ -379,7 +379,7 @@ Get all entries (virtual derived state) cleanup(): Promise; ``` -Defined in: [packages/db/src/collection/index.ts:988](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L988) +Defined in: [packages/db/src/collection/index.ts:989](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L989) Clean up the collection by stopping sync and clearing data This can be called manually or automatically by garbage collection @@ -400,7 +400,7 @@ This can be called manually or automatically by garbage collection createIndex(indexCallback, config): BaseIndex; ``` -Defined in: [packages/db/src/collection/index.ts:597](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L597) +Defined in: [packages/db/src/collection/index.ts:598](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L598) Creates an index on a collection for faster queries. Indexes significantly improve query performance by allowing constant time lookups @@ -460,7 +460,7 @@ currentStateAsChanges(options): | ChangeMessage, string | number>[]; ``` -Defined in: [packages/db/src/collection/index.ts:887](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L887) +Defined in: [packages/db/src/collection/index.ts:888](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L888) Returns the current state of the collection as an array of changes @@ -508,7 +508,7 @@ const activeChanges = collection.currentStateAsChanges({ delete(keys, config?): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:797](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L797) +Defined in: [packages/db/src/collection/index.ts:798](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L798) Deletes one or more items from the collection @@ -575,7 +575,7 @@ try { entries(): IterableIterator<[TKey, WithVirtualProps]>; ``` -Defined in: [packages/db/src/collection/index.ts:519](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L519) +Defined in: [packages/db/src/collection/index.ts:520](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L520) Get all entries (virtual derived state) @@ -595,7 +595,7 @@ Get all entries (virtual derived state) forEach(callbackfn): void; ``` -Defined in: [packages/db/src/collection/index.ts:540](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L540) +Defined in: [packages/db/src/collection/index.ts:541](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L541) Execute a callback for each entry in the collection @@ -623,7 +623,7 @@ get(key): | undefined; ``` -Defined in: [packages/db/src/collection/index.ts:479](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L479) +Defined in: [packages/db/src/collection/index.ts:480](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L480) Get the current value for a key (virtual derived state) @@ -650,7 +650,7 @@ Get the current value for a key (virtual derived state) getIndexMetadata(): CollectionIndexMetadata[]; ``` -Defined in: [packages/db/src/collection/index.ts:621](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L621) +Defined in: [packages/db/src/collection/index.ts:622](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L622) Returns a snapshot of current index metadata sorted by indexId. Persistence wrappers can use this to bootstrap index state if indexes were @@ -672,7 +672,7 @@ created before event listeners were attached. getKeyFromItem(item): TKey; ``` -Defined in: [packages/db/src/collection/index.ts:571](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L571) +Defined in: [packages/db/src/collection/index.ts:572](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L572) #### Parameters @@ -696,7 +696,7 @@ Defined in: [packages/db/src/collection/index.ts:571](https://github.com/TanStac has(key): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:486](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L486) +Defined in: [packages/db/src/collection/index.ts:487](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L487) Check if a key exists in the collection (virtual derived state) @@ -722,7 +722,7 @@ Check if a key exists in the collection (virtual derived state) insert(data, config?): Transaction>; ``` -Defined in: [packages/db/src/collection/index.ts:684](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L684) +Defined in: [packages/db/src/collection/index.ts:685](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L685) Inserts one or more items into the collection @@ -796,7 +796,7 @@ try { isReady(): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:448](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L448) +Defined in: [packages/db/src/collection/index.ts:449](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L449) Check if the collection is ready for use Returns true if the collection has been marked as ready by its sync implementation @@ -830,7 +830,7 @@ if (collection.isReady()) { keys(): IterableIterator; ``` -Defined in: [packages/db/src/collection/index.ts:500](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L500) +Defined in: [packages/db/src/collection/index.ts:501](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L501) Get all keys (virtual derived state) @@ -850,7 +850,7 @@ Get all keys (virtual derived state) map(callbackfn): U[]; ``` -Defined in: [packages/db/src/collection/index.ts:556](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L556) +Defined in: [packages/db/src/collection/index.ts:557](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L557) Create a new array with the results of calling a function for each entry in the collection @@ -882,7 +882,7 @@ Create a new array with the results of calling a function for each entry in the off(event, callback): void; ``` -Defined in: [packages/db/src/collection/index.ts:967](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L967) +Defined in: [packages/db/src/collection/index.ts:968](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L968) Unsubscribe from a collection event @@ -929,7 +929,7 @@ Unsubscribe from a collection event on(event, callback): () => void; ``` -Defined in: [packages/db/src/collection/index.ts:947](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L947) +Defined in: [packages/db/src/collection/index.ts:948](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L948) Subscribe to a collection event @@ -982,7 +982,7 @@ Subscribe to a collection event once(event, callback): () => void; ``` -Defined in: [packages/db/src/collection/index.ts:957](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L957) +Defined in: [packages/db/src/collection/index.ts:958](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L958) Subscribe to a collection event once @@ -1035,7 +1035,7 @@ Subscribe to a collection event once onFirstReady(callback): void; ``` -Defined in: [packages/db/src/collection/index.ts:432](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L432) +Defined in: [packages/db/src/collection/index.ts:433](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L433) Register a callback to be executed when the collection first becomes ready Useful for preloading collections @@ -1073,7 +1073,7 @@ collection.onFirstReady(() => { preload(): Promise; ``` -Defined in: [packages/db/src/collection/index.ts:472](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L472) +Defined in: [packages/db/src/collection/index.ts:473](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L473) Preload the collection data by starting sync if not already started Multiple concurrent calls will share the same promise @@ -1094,7 +1094,7 @@ Multiple concurrent calls will share the same promise removeIndex(indexOrId): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:612](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L612) +Defined in: [packages/db/src/collection/index.ts:613](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L613) Removes an index created with createIndex. Returns true when an index existed and was removed. @@ -1125,7 +1125,7 @@ as invalid after removal. startSyncImmediate(): void; ``` -Defined in: [packages/db/src/collection/index.ts:464](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L464) +Defined in: [packages/db/src/collection/index.ts:465](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L465) Start sync immediately - internal method for compiled queries This bypasses lazy loading for special cases like live query results @@ -1146,7 +1146,7 @@ This bypasses lazy loading for special cases like live query results stateWhenReady(): Promise>>; ``` -Defined in: [packages/db/src/collection/index.ts:834](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L834) +Defined in: [packages/db/src/collection/index.ts:835](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L835) Gets the current state of the collection as a Map, but only resolves when data is available Waits for the first sync commit to complete before resolving @@ -1169,7 +1169,7 @@ Promise that resolves to a Map containing all items in the collection subscribeChanges(callback, options): CollectionSubscription; ``` -Defined in: [packages/db/src/collection/index.ts:935](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L935) +Defined in: [packages/db/src/collection/index.ts:936](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L936) Subscribe to changes in the collection @@ -1248,7 +1248,7 @@ const subscription = collection.subscribeChanges((changes) => { toArrayWhenReady(): Promise[]>; ``` -Defined in: [packages/db/src/collection/index.ts:859](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L859) +Defined in: [packages/db/src/collection/index.ts:860](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L860) Gets the current state of the collection as an Array, but only resolves when data is available Waits for the first sync commit to complete before resolving @@ -1273,7 +1273,7 @@ Promise that resolves to an Array containing all items in the collection update(key, callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:729](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L729) +Defined in: [packages/db/src/collection/index.ts:730](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L730) Updates one or more items in the collection using a callback function @@ -1348,7 +1348,7 @@ update( callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:735](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L735) +Defined in: [packages/db/src/collection/index.ts:736](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L736) Updates one or more items in the collection using a callback function @@ -1426,7 +1426,7 @@ try { update(id, callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:742](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L742) +Defined in: [packages/db/src/collection/index.ts:743](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L743) Updates one or more items in the collection using a callback function @@ -1501,7 +1501,7 @@ update( callback): Transaction; ``` -Defined in: [packages/db/src/collection/index.ts:748](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L748) +Defined in: [packages/db/src/collection/index.ts:749](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L749) Updates one or more items in the collection using a callback function @@ -1582,7 +1582,7 @@ validateData( key?): T; ``` -Defined in: [packages/db/src/collection/index.ts:635](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L635) +Defined in: [packages/db/src/collection/index.ts:636](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L636) Validates the data against the schema @@ -1616,7 +1616,7 @@ Validates the data against the schema values(): IterableIterator>; ``` -Defined in: [packages/db/src/collection/index.ts:507](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L507) +Defined in: [packages/db/src/collection/index.ts:508](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L508) Get all values (virtual derived state) @@ -1636,7 +1636,7 @@ Get all values (virtual derived state) waitFor(event, timeout?): Promise; ``` -Defined in: [packages/db/src/collection/index.ts:977](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L977) +Defined in: [packages/db/src/collection/index.ts:978](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L978) Wait for a collection event diff --git a/docs/reference/interfaces/CollectionLike.md b/docs/reference/interfaces/CollectionLike.md index cc5593c1d1..aa91a8b88b 100644 --- a/docs/reference/interfaces/CollectionLike.md +++ b/docs/reference/interfaces/CollectionLike.md @@ -32,7 +32,7 @@ for the change events system to work compareOptions: StringCollationConfig; ``` -Defined in: [packages/db/src/collection/index.ts:643](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L643) +Defined in: [packages/db/src/collection/index.ts:644](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L644) #### Inherited from @@ -46,7 +46,7 @@ Defined in: [packages/db/src/collection/index.ts:643](https://github.com/TanStac id: string; ``` -Defined in: [packages/db/src/collection/index.ts:279](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L279) +Defined in: [packages/db/src/collection/index.ts:280](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L280) #### Inherited from @@ -60,7 +60,7 @@ Defined in: [packages/db/src/collection/index.ts:279](https://github.com/TanStac indexes: Map>; ``` -Defined in: [packages/db/src/collection/index.ts:628](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L628) +Defined in: [packages/db/src/collection/index.ts:629](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L629) #### Inherited from @@ -76,7 +76,7 @@ Pick.indexes entries(): IterableIterator<[TKey, WithVirtualProps]>; ``` -Defined in: [packages/db/src/collection/index.ts:519](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L519) +Defined in: [packages/db/src/collection/index.ts:520](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L520) Get all entries (virtual derived state) @@ -100,7 +100,7 @@ get(key): | undefined; ``` -Defined in: [packages/db/src/collection/index.ts:479](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L479) +Defined in: [packages/db/src/collection/index.ts:480](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L480) Get the current value for a key (virtual derived state) @@ -129,7 +129,7 @@ Pick.get has(key): boolean; ``` -Defined in: [packages/db/src/collection/index.ts:486](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L486) +Defined in: [packages/db/src/collection/index.ts:487](https://github.com/TanStack/db/blob/main/packages/db/src/collection/index.ts#L487) Check if a key exists in the collection (virtual derived state) diff --git a/docs/reference/interfaces/Context.md b/docs/reference/interfaces/Context.md index 2caa57dab1..bf497cf24e 100644 --- a/docs/reference/interfaces/Context.md +++ b/docs/reference/interfaces/Context.md @@ -5,7 +5,7 @@ title: Context # Interface: Context -Defined in: [packages/db/src/query/builder/types.ts:37](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L37) +Defined in: [packages/db/src/query/builder/types.ts:43](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L43) Context - The central state container for query builder operations @@ -16,7 +16,8 @@ This interface tracks all the information needed to build and type-check queries - `schema`: Current available tables (expands with joins, contracts with subqueries) **Query State**: -- `fromSourceName`: Which table was used in `from()` - needed for optionality logic +- `fromSourceName`: Which table was used in `from()` or the first + `unionAll()` source - needed for optionality logic - `hasJoins`: Whether any joins have been added (affects result type inference) - `joinTypes`: Maps table aliases to their join types for optionality calculations @@ -36,7 +37,7 @@ The context evolves through the query builder chain: baseSchema: ContextSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:39](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L39) +Defined in: [packages/db/src/query/builder/types.ts:45](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L45) *** @@ -46,7 +47,17 @@ Defined in: [packages/db/src/query/builder/types.ts:39](https://github.com/TanSt fromSourceName: string; ``` -Defined in: [packages/db/src/query/builder/types.ts:43](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L43) +Defined in: [packages/db/src/query/builder/types.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L51) + +*** + +### fromSourceNames? + +```ts +optional fromSourceNames: readonly string[]; +``` + +Defined in: [packages/db/src/query/builder/types.ts:53](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L53) *** @@ -56,7 +67,7 @@ Defined in: [packages/db/src/query/builder/types.ts:43](https://github.com/TanSt optional hasJoins: boolean; ``` -Defined in: [packages/db/src/query/builder/types.ts:45](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L45) +Defined in: [packages/db/src/query/builder/types.ts:57](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L57) *** @@ -66,7 +77,17 @@ Defined in: [packages/db/src/query/builder/types.ts:45](https://github.com/TanSt optional hasResult: true; ``` -Defined in: [packages/db/src/query/builder/types.ts:54](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L54) +Defined in: [packages/db/src/query/builder/types.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L66) + +*** + +### hasUnionFrom? + +```ts +optional hasUnionFrom: true; +``` + +Defined in: [packages/db/src/query/builder/types.ts:55](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L55) *** @@ -76,7 +97,17 @@ Defined in: [packages/db/src/query/builder/types.ts:54](https://github.com/TanSt optional joinTypes: Record; ``` -Defined in: [packages/db/src/query/builder/types.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L47) +Defined in: [packages/db/src/query/builder/types.ts:59](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L59) + +*** + +### refsSchema? + +```ts +optional refsSchema: ContextSchema; +``` + +Defined in: [packages/db/src/query/builder/types.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L49) *** @@ -86,7 +117,7 @@ Defined in: [packages/db/src/query/builder/types.ts:47](https://github.com/TanSt optional result: any; ``` -Defined in: [packages/db/src/query/builder/types.ts:52](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L52) +Defined in: [packages/db/src/query/builder/types.ts:64](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L64) *** @@ -96,7 +127,7 @@ Defined in: [packages/db/src/query/builder/types.ts:52](https://github.com/TanSt schema: ContextSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:41](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L41) +Defined in: [packages/db/src/query/builder/types.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L47) *** @@ -106,4 +137,4 @@ Defined in: [packages/db/src/query/builder/types.ts:41](https://github.com/TanSt optional singleResult: boolean; ``` -Defined in: [packages/db/src/query/builder/types.ts:56](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L56) +Defined in: [packages/db/src/query/builder/types.ts:68](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L68) diff --git a/docs/reference/interfaces/LocalOnlyCollectionConfig.md b/docs/reference/interfaces/LocalOnlyCollectionConfig.md index e948a015e3..1cfeb8e17e 100644 --- a/docs/reference/interfaces/LocalOnlyCollectionConfig.md +++ b/docs/reference/interfaces/LocalOnlyCollectionConfig.md @@ -5,7 +5,7 @@ title: LocalOnlyCollectionConfig # Interface: LocalOnlyCollectionConfig\ -Defined in: [packages/db/src/local-only.ts:22](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L22) +Defined in: [packages/db/src/local-only.ts:23](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L23) Configuration interface for Local-only collection options @@ -221,7 +221,7 @@ Defined in: [packages/db/src/types.ts:534](https://github.com/TanStack/db/blob/m optional initialData: T[]; ``` -Defined in: [packages/db/src/local-only.ts:34](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L34) +Defined in: [packages/db/src/local-only.ts:35](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L35) Optional initial data to populate the collection with on creation This data will be applied during the initial sync process diff --git a/docs/reference/interfaces/LocalOnlyCollectionUtils.md b/docs/reference/interfaces/LocalOnlyCollectionUtils.md index c86fbc07b0..8065738560 100644 --- a/docs/reference/interfaces/LocalOnlyCollectionUtils.md +++ b/docs/reference/interfaces/LocalOnlyCollectionUtils.md @@ -5,7 +5,7 @@ title: LocalOnlyCollectionUtils # Interface: LocalOnlyCollectionUtils -Defined in: [packages/db/src/local-only.ts:40](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L40) +Defined in: [packages/db/src/local-only.ts:41](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L41) Local-only collection utilities type @@ -27,7 +27,7 @@ Local-only collection utilities type acceptMutations: (transaction) => void; ``` -Defined in: [packages/db/src/local-only.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L58) +Defined in: [packages/db/src/local-only.ts:59](https://github.com/TanStack/db/blob/main/packages/db/src/local-only.ts#L59) Accepts mutations from a transaction that belong to this collection and persists them. This should be called in your transaction's mutationFn to persist local-only data. diff --git a/docs/reference/interfaces/LocalStorageCollectionConfig.md b/docs/reference/interfaces/LocalStorageCollectionConfig.md index f6884ecefb..46dd151a73 100644 --- a/docs/reference/interfaces/LocalStorageCollectionConfig.md +++ b/docs/reference/interfaces/LocalStorageCollectionConfig.md @@ -5,7 +5,7 @@ title: LocalStorageCollectionConfig # Interface: LocalStorageCollectionConfig\ -Defined in: [packages/db/src/local-storage.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L58) +Defined in: [packages/db/src/local-storage.ts:59](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L59) Configuration interface for localStorage collection options @@ -433,7 +433,7 @@ onUpdate: async ({ transaction, collection }) => { optional parser: Parser; ``` -Defined in: [packages/db/src/local-storage.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L84) +Defined in: [packages/db/src/local-storage.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L85) Parser to use for serializing and deserializing data to and from storage Defaults to JSON @@ -487,7 +487,7 @@ false optional storage: StorageApi; ``` -Defined in: [packages/db/src/local-storage.ts:72](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L72) +Defined in: [packages/db/src/local-storage.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L73) Storage API to use (defaults to window.localStorage) Can be any object that implements the Storage interface (e.g., sessionStorage) @@ -500,7 +500,7 @@ Can be any object that implements the Storage interface (e.g., sessionStorage) optional storageEventApi: StorageEventApi; ``` -Defined in: [packages/db/src/local-storage.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L78) +Defined in: [packages/db/src/local-storage.ts:79](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L79) Storage event API to use for cross-tab synchronization (defaults to window) Can be any object that implements addEventListener/removeEventListener for storage events @@ -513,7 +513,7 @@ Can be any object that implements addEventListener/removeEventListener for stora storageKey: string; ``` -Defined in: [packages/db/src/local-storage.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L66) +Defined in: [packages/db/src/local-storage.ts:67](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L67) The key to use for storing the collection data in localStorage/sessionStorage diff --git a/docs/reference/interfaces/LocalStorageCollectionUtils.md b/docs/reference/interfaces/LocalStorageCollectionUtils.md index 9472f18f60..92a08f99fa 100644 --- a/docs/reference/interfaces/LocalStorageCollectionUtils.md +++ b/docs/reference/interfaces/LocalStorageCollectionUtils.md @@ -5,7 +5,7 @@ title: LocalStorageCollectionUtils # Interface: LocalStorageCollectionUtils -Defined in: [packages/db/src/local-storage.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L100) +Defined in: [packages/db/src/local-storage.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L101) LocalStorage collection utilities type @@ -27,7 +27,7 @@ LocalStorage collection utilities type acceptMutations: (transaction) => void; ``` -Defined in: [packages/db/src/local-storage.ts:120](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L120) +Defined in: [packages/db/src/local-storage.ts:121](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L121) Accepts mutations from a transaction that belong to this collection and persists them to localStorage. This should be called in your transaction's mutationFn to persist local-storage data. @@ -69,7 +69,7 @@ const tx = createTransaction({ clearStorage: ClearStorageFn; ``` -Defined in: [packages/db/src/local-storage.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L101) +Defined in: [packages/db/src/local-storage.ts:102](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L102) *** @@ -79,4 +79,4 @@ Defined in: [packages/db/src/local-storage.ts:101](https://github.com/TanStack/d getStorageSize: GetStorageSizeFn; ``` -Defined in: [packages/db/src/local-storage.ts:102](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L102) +Defined in: [packages/db/src/local-storage.ts:103](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L103) diff --git a/docs/reference/interfaces/ParseWhereOptions.md b/docs/reference/interfaces/ParseWhereOptions.md index 8a90e90142..5fee3544ef 100644 --- a/docs/reference/interfaces/ParseWhereOptions.md +++ b/docs/reference/interfaces/ParseWhereOptions.md @@ -87,6 +87,22 @@ optional avg: (...args) => T; `T` +##### caseWhen()? + +```ts +optional caseWhen: (...args) => T; +``` + +###### Parameters + +###### args + +...`any`[] + +###### Returns + +`T` + ##### coalesce()? ```ts @@ -135,6 +151,22 @@ optional count: (...args) => T; `T` +##### divide()? + +```ts +optional divide: (...args) => T; +``` + +###### Parameters + +###### args + +...`any`[] + +###### Returns + +`T` + ##### eq()? ```ts @@ -359,6 +391,22 @@ optional min: (...args) => T; `T` +##### multiply()? + +```ts +optional multiply: (...args) => T; +``` + +###### Parameters + +###### args + +...`any`[] + +###### Returns + +`T` + ##### not()? ```ts @@ -391,6 +439,22 @@ optional or: (...args) => T; `T` +##### subtract()? + +```ts +optional subtract: (...args) => T; +``` + +###### Parameters + +###### args + +...`any`[] + +###### Returns + +`T` + ##### sum()? ```ts diff --git a/docs/reference/interfaces/Parser.md b/docs/reference/interfaces/Parser.md index d1eafc24c7..1db679a980 100644 --- a/docs/reference/interfaces/Parser.md +++ b/docs/reference/interfaces/Parser.md @@ -5,7 +5,7 @@ title: Parser # Interface: Parser -Defined in: [packages/db/src/local-storage.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L47) +Defined in: [packages/db/src/local-storage.ts:48](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L48) ## Properties @@ -15,7 +15,7 @@ Defined in: [packages/db/src/local-storage.ts:47](https://github.com/TanStack/db parse: (data) => unknown; ``` -Defined in: [packages/db/src/local-storage.ts:48](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L48) +Defined in: [packages/db/src/local-storage.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L49) #### Parameters @@ -35,7 +35,7 @@ Defined in: [packages/db/src/local-storage.ts:48](https://github.com/TanStack/db stringify: (data) => string; ``` -Defined in: [packages/db/src/local-storage.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L49) +Defined in: [packages/db/src/local-storage.ts:50](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L50) #### Parameters diff --git a/docs/reference/interfaces/Transaction.md b/docs/reference/interfaces/Transaction.md index cffa506996..b5f6c3b4da 100644 --- a/docs/reference/interfaces/Transaction.md +++ b/docs/reference/interfaces/Transaction.md @@ -5,7 +5,7 @@ title: Transaction # Interface: Transaction\ -Defined in: [packages/db/src/transactions.ts:208](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L208) +Defined in: [packages/db/src/transactions.ts:209](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L209) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [packages/db/src/transactions.ts:208](https://github.com/TanStack/db autoCommit: boolean; ``` -Defined in: [packages/db/src/transactions.ts:214](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L214) +Defined in: [packages/db/src/transactions.ts:215](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L215) *** @@ -31,7 +31,7 @@ Defined in: [packages/db/src/transactions.ts:214](https://github.com/TanStack/db createdAt: Date; ``` -Defined in: [packages/db/src/transactions.ts:215](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L215) +Defined in: [packages/db/src/transactions.ts:216](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L216) *** @@ -41,7 +41,7 @@ Defined in: [packages/db/src/transactions.ts:215](https://github.com/TanStack/db optional error: object; ``` -Defined in: [packages/db/src/transactions.ts:218](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L218) +Defined in: [packages/db/src/transactions.ts:219](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L219) #### error @@ -63,7 +63,7 @@ message: string; id: string; ``` -Defined in: [packages/db/src/transactions.ts:209](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L209) +Defined in: [packages/db/src/transactions.ts:210](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L210) *** @@ -73,7 +73,7 @@ Defined in: [packages/db/src/transactions.ts:209](https://github.com/TanStack/db isPersisted: Deferred>; ``` -Defined in: [packages/db/src/transactions.ts:213](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L213) +Defined in: [packages/db/src/transactions.ts:214](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L214) *** @@ -83,7 +83,7 @@ Defined in: [packages/db/src/transactions.ts:213](https://github.com/TanStack/db metadata: Record; ``` -Defined in: [packages/db/src/transactions.ts:217](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L217) +Defined in: [packages/db/src/transactions.ts:218](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L218) *** @@ -93,7 +93,7 @@ Defined in: [packages/db/src/transactions.ts:217](https://github.com/TanStack/db mutationFn: MutationFn; ``` -Defined in: [packages/db/src/transactions.ts:211](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L211) +Defined in: [packages/db/src/transactions.ts:212](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L212) *** @@ -103,7 +103,7 @@ Defined in: [packages/db/src/transactions.ts:211](https://github.com/TanStack/db mutations: PendingMutation>[]; ``` -Defined in: [packages/db/src/transactions.ts:212](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L212) +Defined in: [packages/db/src/transactions.ts:213](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L213) *** @@ -113,7 +113,7 @@ Defined in: [packages/db/src/transactions.ts:212](https://github.com/TanStack/db sequenceNumber: number; ``` -Defined in: [packages/db/src/transactions.ts:216](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L216) +Defined in: [packages/db/src/transactions.ts:217](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L217) *** @@ -123,7 +123,7 @@ Defined in: [packages/db/src/transactions.ts:216](https://github.com/TanStack/db state: TransactionState; ``` -Defined in: [packages/db/src/transactions.ts:210](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L210) +Defined in: [packages/db/src/transactions.ts:211](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L211) ## Methods @@ -133,7 +133,7 @@ Defined in: [packages/db/src/transactions.ts:210](https://github.com/TanStack/db applyMutations(mutations): void; ``` -Defined in: [packages/db/src/transactions.ts:327](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L327) +Defined in: [packages/db/src/transactions.ts:336](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L336) Apply new mutations to this transaction, intelligently merging with existing mutations @@ -169,7 +169,7 @@ Array of new mutations to apply commit(): Promise>; ``` -Defined in: [packages/db/src/transactions.ts:472](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L472) +Defined in: [packages/db/src/transactions.ts:481](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L481) Commit the transaction and execute the mutation function @@ -228,7 +228,7 @@ console.log(tx.state) // "completed" or "failed" compareCreatedAt(other): number; ``` -Defined in: [packages/db/src/transactions.ts:526](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L526) +Defined in: [packages/db/src/transactions.ts:535](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L535) Compare two transactions by their createdAt time and sequence number in order to sort them in the order they were created. @@ -255,7 +255,7 @@ The other transaction to compare to mutate(callback): Transaction; ``` -Defined in: [packages/db/src/transactions.ts:287](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L287) +Defined in: [packages/db/src/transactions.ts:296](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L296) Execute collection operations within this transaction @@ -265,9 +265,12 @@ Execute collection operations within this transaction () => `void` -Function containing collection operations to group together. If the -callback returns a Promise, the transaction context will remain active until the promise -settles, allowing optimistic writes after `await` boundaries. +Synchronous function containing collection operations to group together. +The transaction context is active only for the synchronous duration of this callback. +Async work should happen in `mutationFn`; collection operations after `await` boundaries +inside this callback will not be part of this transaction. For manual transactions, call +`mutate` multiple times before committing to add more synchronous operations to the same +transaction. #### Returns @@ -311,6 +314,11 @@ tx.mutate(() => { collection.insert({ id: "1", text: "Item" }) }) +// Add more synchronous mutations to the same transaction +tx.mutate(() => { + collection.update("1", draft => { draft.text = "Updated item" }) +}) + // Commit later when ready await tx.commit() ``` @@ -323,7 +331,7 @@ await tx.commit() rollback(config?): Transaction; ``` -Defined in: [packages/db/src/transactions.ts:389](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L389) +Defined in: [packages/db/src/transactions.ts:398](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L398) Rollback the transaction and any conflicting transactions @@ -390,7 +398,7 @@ try { setState(newState): void; ``` -Defined in: [packages/db/src/transactions.ts:238](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L238) +Defined in: [packages/db/src/transactions.ts:239](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L239) #### Parameters @@ -410,7 +418,7 @@ Defined in: [packages/db/src/transactions.ts:238](https://github.com/TanStack/db touchCollection(): void; ``` -Defined in: [packages/db/src/transactions.ts:417](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L417) +Defined in: [packages/db/src/transactions.ts:426](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L426) #### Returns diff --git a/docs/reference/query-db-collection/functions/queryCollectionOptions.md b/docs/reference/query-db-collection/functions/queryCollectionOptions.md index 360652b593..06c48a6d49 100644 --- a/docs/reference/query-db-collection/functions/queryCollectionOptions.md +++ b/docs/reference/query-db-collection/functions/queryCollectionOptions.md @@ -11,7 +11,7 @@ title: queryCollectionOptions function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:431](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L431) +Defined in: [packages/query-db-collection/src/query.ts:438](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L438) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -151,7 +151,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:466](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L466) +Defined in: [packages/query-db-collection/src/query.ts:473](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L473) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -291,7 +291,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:499](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L499) +Defined in: [packages/query-db-collection/src/query.ts:506](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L506) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -423,7 +423,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:533](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L533) +Defined in: [packages/query-db-collection/src/query.ts:540](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L540) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. diff --git a/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md b/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md index a53cae230d..1888ec8ebd 100644 --- a/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md +++ b/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md @@ -69,13 +69,32 @@ Whether the query should automatically run (default: true) *** +### gcTime? + +```ts +optional gcTime: number; +``` + +Defined in: [packages/query-db-collection/src/query.ts:122](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L122) + +Time in milliseconds after which the collection will be garbage collected +when it has no active subscribers. Defaults to 5 minutes (300000ms). + +#### Overrides + +```ts +BaseCollectionConfig.gcTime +``` + +*** + ### meta? ```ts optional meta: Record; ``` -Defined in: [packages/query-db-collection/src/query.ts:144](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L144) +Defined in: [packages/query-db-collection/src/query.ts:151](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L151) Metadata to pass to the query. Available in queryFn via context.meta @@ -107,7 +126,7 @@ meta: { optional persistedGcTime: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:122](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L122) +Defined in: [packages/query-db-collection/src/query.ts:129](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L129) *** diff --git a/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md b/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md index d30be795f5..88bb9c88eb 100644 --- a/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md +++ b/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md @@ -5,7 +5,7 @@ title: QueryCollectionUtils # Interface: QueryCollectionUtils\ -Defined in: [packages/query-db-collection/src/query.ts:163](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L163) +Defined in: [packages/query-db-collection/src/query.ts:170](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L170) Utility methods available on Query Collections for direct writes and manual operations. Direct writes bypass the normal query/mutation flow and write directly to the synced data store. @@ -54,7 +54,7 @@ The type of errors that can occur during queries clearError: () => Promise; ``` -Defined in: [packages/query-db-collection/src/query.ts:208](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L208) +Defined in: [packages/query-db-collection/src/query.ts:215](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L215) Clear the error state and trigger a refetch of the query @@ -76,7 +76,7 @@ Error if the refetch fails dataUpdatedAt: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:199](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L199) +Defined in: [packages/query-db-collection/src/query.ts:206](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L206) Get timestamp of last successful data update (in milliseconds) @@ -88,7 +88,7 @@ Get timestamp of last successful data update (in milliseconds) errorCount: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:191](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L191) +Defined in: [packages/query-db-collection/src/query.ts:198](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L198) Get the number of consecutive sync failures. Incremented only when query fails completely (not per retry attempt); reset on success. @@ -101,7 +101,7 @@ Incremented only when query fails completely (not per retry attempt); reset on s fetchStatus: "idle" | "fetching" | "paused"; ``` -Defined in: [packages/query-db-collection/src/query.ts:201](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L201) +Defined in: [packages/query-db-collection/src/query.ts:208](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L208) Get current fetch status @@ -113,7 +113,7 @@ Get current fetch status isError: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:186](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L186) +Defined in: [packages/query-db-collection/src/query.ts:193](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L193) Check if the collection is in an error state @@ -125,7 +125,7 @@ Check if the collection is in an error state isFetching: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:193](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L193) +Defined in: [packages/query-db-collection/src/query.ts:200](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L200) Check if query is currently fetching (initial or background) @@ -137,7 +137,7 @@ Check if query is currently fetching (initial or background) isLoading: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:197](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L197) +Defined in: [packages/query-db-collection/src/query.ts:204](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L204) Check if query is loading for the first time (no data yet) @@ -149,7 +149,7 @@ Check if query is loading for the first time (no data yet) isRefetching: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:195](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L195) +Defined in: [packages/query-db-collection/src/query.ts:202](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L202) Check if query is refetching in background (not initial fetch) @@ -161,7 +161,7 @@ Check if query is refetching in background (not initial fetch) lastError: TError | undefined; ``` -Defined in: [packages/query-db-collection/src/query.ts:184](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L184) +Defined in: [packages/query-db-collection/src/query.ts:191](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L191) Get the last error encountered by the query (if any); reset on success @@ -173,7 +173,7 @@ Get the last error encountered by the query (if any); reset on success refetch: RefetchFn; ``` -Defined in: [packages/query-db-collection/src/query.ts:170](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L170) +Defined in: [packages/query-db-collection/src/query.ts:177](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L177) Manually trigger a refetch of the query @@ -185,7 +185,7 @@ Manually trigger a refetch of the query writeBatch: (callback) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:180](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L180) +Defined in: [packages/query-db-collection/src/query.ts:187](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L187) Execute multiple write operations as a single atomic batch to the synced data store @@ -207,7 +207,7 @@ Execute multiple write operations as a single atomic batch to the synced data st writeDelete: (keys) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:176](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L176) +Defined in: [packages/query-db-collection/src/query.ts:183](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L183) Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update @@ -229,7 +229,7 @@ Delete one or more items directly from the synced data store without triggering writeInsert: (data) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:172](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L172) +Defined in: [packages/query-db-collection/src/query.ts:179](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L179) Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update @@ -251,7 +251,7 @@ Insert one or more items directly into the synced data store without triggering writeUpdate: (updates) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:174](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L174) +Defined in: [packages/query-db-collection/src/query.ts:181](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L181) Update one or more items directly in the synced data store without triggering a query refetch or optimistic update @@ -273,7 +273,7 @@ Update one or more items directly in the synced data store without triggering a writeUpsert: (data) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:178](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L178) +Defined in: [packages/query-db-collection/src/query.ts:185](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L185) Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update diff --git a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md index de18e0b6d1..7173e38303 100644 --- a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md +++ b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md @@ -3,13 +3,13 @@ id: ApplyJoinOptionalityToMergedSchema title: ApplyJoinOptionalityToMergedSchema --- -# Type Alias: ApplyJoinOptionalityToMergedSchema\ +# Type Alias: ApplyJoinOptionalityToMergedSchema\ ```ts -type ApplyJoinOptionalityToMergedSchema = { [K in keyof TExistingSchema]: K extends TFromSourceName ? TJoinType extends "right" | "full" ? TExistingSchema[K] | undefined : TExistingSchema[K] : TExistingSchema[K] } & { [K in keyof TNewSchema]: TJoinType extends "left" | "full" ? TNewSchema[K] | undefined : TNewSchema[K] }; +type ApplyJoinOptionalityToMergedSchema = { [K in keyof TExistingSchema]: K extends TFromSourceNames ? TJoinType extends "right" | "full" ? TExistingSchema[K] | undefined : TExistingSchema[K] : TExistingSchema[K] } & { [K in keyof TNewSchema]: TJoinType extends "left" | "full" ? TNewSchema[K] | undefined : TNewSchema[K] }; ``` -Defined in: [packages/db/src/query/builder/types.ts:779](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L779) +Defined in: [packages/db/src/query/builder/types.ts:929](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L929) ApplyJoinOptionalityToMergedSchema - Applies optionality rules when merging schemas @@ -49,6 +49,6 @@ into a single type while preserving all table references. `TJoinType` *extends* `"inner"` \| `"left"` \| `"right"` \| `"full"` \| `"outer"` \| `"cross"` -### TFromSourceName +### TFromSourceNames -`TFromSourceName` *extends* `string` +`TFromSourceNames` *extends* `string` diff --git a/docs/reference/type-aliases/ClearStorageFn.md b/docs/reference/type-aliases/ClearStorageFn.md index 05751d0912..699e453215 100644 --- a/docs/reference/type-aliases/ClearStorageFn.md +++ b/docs/reference/type-aliases/ClearStorageFn.md @@ -9,7 +9,7 @@ title: ClearStorageFn type ClearStorageFn = () => void; ``` -Defined in: [packages/db/src/local-storage.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L90) +Defined in: [packages/db/src/local-storage.ts:91](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L91) Type for the clear utility function diff --git a/docs/reference/type-aliases/ContextFromSource.md b/docs/reference/type-aliases/ContextFromSource.md new file mode 100644 index 0000000000..73642c5c0f --- /dev/null +++ b/docs/reference/type-aliases/ContextFromSource.md @@ -0,0 +1,58 @@ +--- +id: ContextFromSource +title: ContextFromSource +--- + +# Type Alias: ContextFromSource\ + +```ts +type ContextFromSource = object; +``` + +Defined in: [packages/db/src/query/builder/types.ts:137](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L137) + +## Type Parameters + +### TSource + +`TSource` *extends* [`Source`](Source.md) + +## Properties + +### baseSchema + +```ts +baseSchema: SchemaFromSource; +``` + +Defined in: [packages/db/src/query/builder/types.ts:138](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L138) + +*** + +### fromSourceName + +```ts +fromSourceName: keyof TSource & string; +``` + +Defined in: [packages/db/src/query/builder/types.ts:140](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L140) + +*** + +### hasJoins + +```ts +hasJoins: false; +``` + +Defined in: [packages/db/src/query/builder/types.ts:141](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L141) + +*** + +### schema + +```ts +schema: SchemaFromSource; +``` + +Defined in: [packages/db/src/query/builder/types.ts:139](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L139) diff --git a/docs/reference/type-aliases/ContextFromUnionBranches.md b/docs/reference/type-aliases/ContextFromUnionBranches.md new file mode 100644 index 0000000000..929bb1b444 --- /dev/null +++ b/docs/reference/type-aliases/ContextFromUnionBranches.md @@ -0,0 +1,88 @@ +--- +id: ContextFromUnionBranches +title: ContextFromUnionBranches +--- + +# Type Alias: ContextFromUnionBranches\ + +```ts +type ContextFromUnionBranches = object; +``` + +Defined in: [packages/db/src/query/builder/types.ts:169](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L169) + +## Type Parameters + +### TBranches + +`TBranches` *extends* readonly \[[`QueryBuilder`](QueryBuilder.md)\<`any`\>, `...QueryBuilder[]`\] + +## Properties + +### baseSchema + +```ts +baseSchema: UnionBranchSchema & ContextSchema; +``` + +Defined in: [packages/db/src/query/builder/types.ts:172](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L172) + +*** + +### fromSourceName + +```ts +fromSourceName: keyof UnionBranchSchema & string; +``` + +Defined in: [packages/db/src/query/builder/types.ts:175](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L175) + +*** + +### hasJoins + +```ts +hasJoins: false; +``` + +Defined in: [packages/db/src/query/builder/types.ts:176](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L176) + +*** + +### hasResult + +```ts +hasResult: true; +``` + +Defined in: [packages/db/src/query/builder/types.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L178) + +*** + +### refsSchema + +```ts +refsSchema: UnionBranchSchema; +``` + +Defined in: [packages/db/src/query/builder/types.ts:174](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L174) + +*** + +### result + +```ts +result: PrettifyIfPlainObject>; +``` + +Defined in: [packages/db/src/query/builder/types.ts:177](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L177) + +*** + +### schema + +```ts +schema: UnionBranchSchema & ContextSchema; +``` + +Defined in: [packages/db/src/query/builder/types.ts:173](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L173) diff --git a/docs/reference/type-aliases/ContextFromUnionSource.md b/docs/reference/type-aliases/ContextFromUnionSource.md new file mode 100644 index 0000000000..79930c1f53 --- /dev/null +++ b/docs/reference/type-aliases/ContextFromUnionSource.md @@ -0,0 +1,18 @@ +--- +id: ContextFromUnionSource +title: ContextFromUnionSource +--- + +# Type Alias: ContextFromUnionSource\ + +```ts +type ContextFromUnionSource = IsUnion extends true ? object : ContextFromSource; +``` + +Defined in: [packages/db/src/query/builder/types.ts:144](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L144) + +## Type Parameters + +### TSource + +`TSource` *extends* [`Source`](Source.md) diff --git a/docs/reference/type-aliases/ContextSchema.md b/docs/reference/type-aliases/ContextSchema.md index 09d96d6a38..ef7c6ed1f0 100644 --- a/docs/reference/type-aliases/ContextSchema.md +++ b/docs/reference/type-aliases/ContextSchema.md @@ -9,7 +9,7 @@ title: ContextSchema type ContextSchema = Record; ``` -Defined in: [packages/db/src/query/builder/types.ts:68](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L68) +Defined in: [packages/db/src/query/builder/types.ts:80](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L80) ContextSchema - The shape of available tables/collections in a query context diff --git a/docs/reference/type-aliases/ExtractContext.md b/docs/reference/type-aliases/ExtractContext.md index 4bbdc55025..d781fe1b57 100644 --- a/docs/reference/type-aliases/ExtractContext.md +++ b/docs/reference/type-aliases/ExtractContext.md @@ -9,7 +9,7 @@ title: ExtractContext type ExtractContext = T extends BaseQueryBuilder ? TContext : T extends QueryBuilder ? TContext : never; ``` -Defined in: [packages/db/src/query/builder/index.ts:1235](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1235) +Defined in: [packages/db/src/query/builder/index.ts:1411](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1411) ## Type Parameters diff --git a/docs/reference/type-aliases/FunctionalHavingRow.md b/docs/reference/type-aliases/FunctionalHavingRow.md index 039184e3c1..e5aaf3ec95 100644 --- a/docs/reference/type-aliases/FunctionalHavingRow.md +++ b/docs/reference/type-aliases/FunctionalHavingRow.md @@ -9,7 +9,7 @@ title: FunctionalHavingRow type FunctionalHavingRow = TContext["schema"] & TContext["hasResult"] extends true ? object : object; ``` -Defined in: [packages/db/src/query/builder/types.ts:483](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L483) +Defined in: [packages/db/src/query/builder/types.ts:589](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L589) FunctionalHavingRow - Type for the row parameter in functional having callbacks diff --git a/docs/reference/type-aliases/GetResult.md b/docs/reference/type-aliases/GetResult.md index 36f2043108..6e2a58c0b0 100644 --- a/docs/reference/type-aliases/GetResult.md +++ b/docs/reference/type-aliases/GetResult.md @@ -9,7 +9,7 @@ title: GetResult type GetResult = Prettify>; ``` -Defined in: [packages/db/src/query/builder/types.ts:848](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L848) +Defined in: [packages/db/src/query/builder/types.ts:1043](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1043) ## Type Parameters diff --git a/docs/reference/type-aliases/GetStorageSizeFn.md b/docs/reference/type-aliases/GetStorageSizeFn.md index 2008fbbd6c..b57ac423b2 100644 --- a/docs/reference/type-aliases/GetStorageSizeFn.md +++ b/docs/reference/type-aliases/GetStorageSizeFn.md @@ -9,7 +9,7 @@ title: GetStorageSizeFn type GetStorageSizeFn = () => number; ``` -Defined in: [packages/db/src/local-storage.ts:95](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L95) +Defined in: [packages/db/src/local-storage.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L96) Type for the getStorageSize utility function diff --git a/docs/reference/type-aliases/GroupByCallback.md b/docs/reference/type-aliases/GroupByCallback.md index d5f6e0d5e9..62b0a172b9 100644 --- a/docs/reference/type-aliases/GroupByCallback.md +++ b/docs/reference/type-aliases/GroupByCallback.md @@ -9,7 +9,7 @@ title: GroupByCallback type GroupByCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:446](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L446) +Defined in: [packages/db/src/query/builder/types.ts:552](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L552) GroupByCallback - Type for groupBy clause callback functions diff --git a/docs/reference/type-aliases/InferCollectionType.md b/docs/reference/type-aliases/InferCollectionType.md index 466fb9aeba..36b19a5fd8 100644 --- a/docs/reference/type-aliases/InferCollectionType.md +++ b/docs/reference/type-aliases/InferCollectionType.md @@ -9,7 +9,7 @@ title: InferCollectionType type InferCollectionType = T extends CollectionImpl ? WithVirtualProps : never; ``` -Defined in: [packages/db/src/query/builder/types.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L89) +Defined in: [packages/db/src/query/builder/types.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L101) InferCollectionType - Extracts the TypeScript type from a CollectionImpl diff --git a/docs/reference/type-aliases/InferResultType.md b/docs/reference/type-aliases/InferResultType.md index 15b1f23375..b822c47b5d 100644 --- a/docs/reference/type-aliases/InferResultType.md +++ b/docs/reference/type-aliases/InferResultType.md @@ -9,7 +9,7 @@ title: InferResultType type InferResultType = TContext extends SingleResult ? GetResult | undefined : GetResult[]; ``` -Defined in: [packages/db/src/query/builder/types.ts:805](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L805) +Defined in: [packages/db/src/query/builder/types.ts:955](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L955) Utility type to infer the query result size (single row or an array) diff --git a/docs/reference/type-aliases/InitialQueryBuilder.md b/docs/reference/type-aliases/InitialQueryBuilder.md index 1308b07fc3..a4de915831 100644 --- a/docs/reference/type-aliases/InitialQueryBuilder.md +++ b/docs/reference/type-aliases/InitialQueryBuilder.md @@ -6,7 +6,7 @@ title: InitialQueryBuilder # Type Alias: InitialQueryBuilder ```ts -type InitialQueryBuilder = Pick, "from">; +type InitialQueryBuilder = Pick, "from" | "unionAll">; ``` -Defined in: [packages/db/src/query/builder/index.ts:1221](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1221) +Defined in: [packages/db/src/query/builder/index.ts:1394](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1394) diff --git a/docs/reference/type-aliases/JoinOnCallback.md b/docs/reference/type-aliases/JoinOnCallback.md index 27ba29f3ad..31566ca223 100644 --- a/docs/reference/type-aliases/JoinOnCallback.md +++ b/docs/reference/type-aliases/JoinOnCallback.md @@ -9,7 +9,7 @@ title: JoinOnCallback type JoinOnCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:462](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L462) +Defined in: [packages/db/src/query/builder/types.ts:568](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L568) JoinOnCallback - Type for join condition callback functions diff --git a/docs/reference/type-aliases/LiveQueryCollectionUtils.md b/docs/reference/type-aliases/LiveQueryCollectionUtils.md index ecb1be4892..bd12ab1281 100644 --- a/docs/reference/type-aliases/LiveQueryCollectionUtils.md +++ b/docs/reference/type-aliases/LiveQueryCollectionUtils.md @@ -9,7 +9,7 @@ title: LiveQueryCollectionUtils type LiveQueryCollectionUtils = UtilsRecord & object; ``` -Defined in: [packages/db/src/query/live/collection-config-builder.ts:54](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/collection-config-builder.ts#L54) +Defined in: [packages/db/src/query/live/collection-config-builder.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/collection-config-builder.ts#L58) ## Type Declaration diff --git a/docs/reference/type-aliases/MergeContextForJoinCallback.md b/docs/reference/type-aliases/MergeContextForJoinCallback.md index b675c42d15..0c0cfa8d35 100644 --- a/docs/reference/type-aliases/MergeContextForJoinCallback.md +++ b/docs/reference/type-aliases/MergeContextForJoinCallback.md @@ -6,10 +6,10 @@ title: MergeContextForJoinCallback # Type Alias: MergeContextForJoinCallback\ ```ts -type MergeContextForJoinCallback = object & PreserveHasResultFlag; +type MergeContextForJoinCallback = object & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; ``` -Defined in: [packages/db/src/query/builder/types.ts:1005](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1005) +Defined in: [packages/db/src/query/builder/types.ts:1200](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1200) MergeContextForJoinCallback - Special context for join condition callbacks @@ -63,6 +63,12 @@ hasJoins: true; joinTypes: TContext["joinTypes"] extends Record ? TContext["joinTypes"] : object; ``` +### refsSchema + +```ts +refsSchema: RefsSchemaForContext & TNewSchema; +``` + ### result ```ts diff --git a/docs/reference/type-aliases/MergeContextWithJoinType.md b/docs/reference/type-aliases/MergeContextWithJoinType.md index 983e847180..0ea2e91d31 100644 --- a/docs/reference/type-aliases/MergeContextWithJoinType.md +++ b/docs/reference/type-aliases/MergeContextWithJoinType.md @@ -6,10 +6,10 @@ title: MergeContextWithJoinType # Type Alias: MergeContextWithJoinType\ ```ts -type MergeContextWithJoinType = object & PreserveSingleResultFlag & PreserveHasResultFlag; +type MergeContextWithJoinType = object & PreserveSingleResultFlag & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; ``` -Defined in: [packages/db/src/query/builder/types.ts:729](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L729) +Defined in: [packages/db/src/query/builder/types.ts:871](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L871) MergeContextWithJoinType - Creates a new context after a join operation @@ -59,6 +59,12 @@ hasJoins: true; joinTypes: TContext["joinTypes"] extends Record ? TContext["joinTypes"] : object & { [K in keyof TNewSchema & string]: TJoinType }; ``` +### refsSchema + +```ts +refsSchema: ApplyJoinOptionalityToMergedSchema, TNewSchema, TJoinType, FromSourceNamesForOptionality>; +``` + ### result ```ts @@ -68,7 +74,7 @@ result: TContext["result"]; ### schema ```ts -schema: ApplyJoinOptionalityToMergedSchema; +schema: ApplyJoinOptionalityToMergedSchema>; ``` ## Type Parameters diff --git a/docs/reference/type-aliases/OperatorName.md b/docs/reference/type-aliases/OperatorName.md index 92c072d006..695ed994ab 100644 --- a/docs/reference/type-aliases/OperatorName.md +++ b/docs/reference/type-aliases/OperatorName.md @@ -9,4 +9,4 @@ title: OperatorName type OperatorName = typeof operators[number]; ``` -Defined in: [packages/db/src/query/builder/functions.ts:437](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L437) +Defined in: [packages/db/src/query/builder/functions.ts:718](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L718) diff --git a/docs/reference/type-aliases/OrderByCallback.md b/docs/reference/type-aliases/OrderByCallback.md index b58fde0d12..05f7bc5d94 100644 --- a/docs/reference/type-aliases/OrderByCallback.md +++ b/docs/reference/type-aliases/OrderByCallback.md @@ -9,7 +9,7 @@ title: OrderByCallback type OrderByCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:410](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L410) +Defined in: [packages/db/src/query/builder/types.ts:516](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L516) OrderByCallback - Type for orderBy clause callback functions diff --git a/docs/reference/type-aliases/Prettify.md b/docs/reference/type-aliases/Prettify.md index 673257fd9a..2c77515148 100644 --- a/docs/reference/type-aliases/Prettify.md +++ b/docs/reference/type-aliases/Prettify.md @@ -9,7 +9,7 @@ title: Prettify type Prettify = { [K in keyof T]: T[K] } & object; ``` -Defined in: [packages/db/src/query/builder/types.ts:1044](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1044) +Defined in: [packages/db/src/query/builder/types.ts:1242](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1242) Prettify - Utility type for clean IDE display diff --git a/docs/reference/type-aliases/QueryBuilder.md b/docs/reference/type-aliases/QueryBuilder.md index 060999bb6c..790ca0702c 100644 --- a/docs/reference/type-aliases/QueryBuilder.md +++ b/docs/reference/type-aliases/QueryBuilder.md @@ -6,10 +6,10 @@ title: QueryBuilder # Type Alias: QueryBuilder\ ```ts -type QueryBuilder = Omit, "from" | "_getQuery">; +type QueryBuilder = Omit, "from" | "unionAll" | "_getQuery">; ``` -Defined in: [packages/db/src/query/builder/index.ts:1225](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1225) +Defined in: [packages/db/src/query/builder/index.ts:1401](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1401) ## Type Parameters diff --git a/docs/reference/type-aliases/QueryResult.md b/docs/reference/type-aliases/QueryResult.md index 26cecaf634..6d86f7a989 100644 --- a/docs/reference/type-aliases/QueryResult.md +++ b/docs/reference/type-aliases/QueryResult.md @@ -9,7 +9,7 @@ title: QueryResult type QueryResult = GetResult>; ``` -Defined in: [packages/db/src/query/builder/index.ts:1243](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1243) +Defined in: [packages/db/src/query/builder/index.ts:1419](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1419) ## Type Parameters diff --git a/docs/reference/type-aliases/Ref.md b/docs/reference/type-aliases/Ref.md index fce38d02f4..be97f893e9 100644 --- a/docs/reference/type-aliases/Ref.md +++ b/docs/reference/type-aliases/Ref.md @@ -9,7 +9,7 @@ title: Ref type Ref = { [K in keyof T]: IsNonExactOptional extends true ? IsNonExactNullable extends true ? IsPlainObject> extends true ? Ref, Nullable> | undefined : RefLeaf, Nullable> | undefined : IsPlainObject> extends true ? Ref, Nullable> | undefined : RefLeaf, Nullable> | undefined : IsNonExactNullable extends true ? IsPlainObject> extends true ? Ref, Nullable> | null : RefLeaf, Nullable> | null : IsPlainObject extends true ? Ref : RefLeaf } & RefLeaf & VirtualPropsRef; ``` -Defined in: [packages/db/src/query/builder/types.ts:639](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L639) +Defined in: [packages/db/src/query/builder/types.ts:773](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L773) Ref - The user-facing ref interface for the query builder diff --git a/docs/reference/type-aliases/RefsForContext.md b/docs/reference/type-aliases/RefsForContext.md index 3476398e73..6eeb1b161f 100644 --- a/docs/reference/type-aliases/RefsForContext.md +++ b/docs/reference/type-aliases/RefsForContext.md @@ -6,25 +6,10 @@ title: RefsForContext # Type Alias: RefsForContext\ ```ts -type RefsForContext = { [K in keyof TContext["schema"]]: IsNonExactOptional extends true ? IsNonExactNullable extends true ? Ref, true> : Ref, true> : IsNonExactNullable extends true ? Ref, true> : Ref } & TContext["hasResult"] extends true ? object : object; +type RefsForContext = { [K in KeysOfUnion>]: IsNonExactOptional, K>> extends true ? IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>>, true> : IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>> } & TContext["hasResult"] extends true ? object : object; ``` -Defined in: [packages/db/src/query/builder/types.ts:502](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L502) - -RefsForContext - Creates ref proxies for all tables/collections in a query context - -This is the main entry point for creating ref objects in query builder callbacks. -For nullable join sides (left/right/full joins), it produces `Ref` instead -of `Ref | undefined`. This accurately reflects that the proxy object is always -present at build time (it's a truthy proxy that records property access paths), -while the `Nullable` flag ensures the result type correctly includes `| undefined`. - -Examples: -- Required field: `Ref` → user.name works, result is T -- Nullable join side: `Ref` → user.name works, result is T | undefined - -After `select()` is called, this type also includes `$selected` which provides access -to the SELECT result fields via `$selected.fieldName` syntax. +Defined in: [packages/db/src/query/builder/types.ts:623](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L623) ## Type Parameters diff --git a/docs/reference/type-aliases/ResultTypeFromSelect.md b/docs/reference/type-aliases/ResultTypeFromSelect.md index dd7e568aad..ab73347331 100644 --- a/docs/reference/type-aliases/ResultTypeFromSelect.md +++ b/docs/reference/type-aliases/ResultTypeFromSelect.md @@ -6,10 +6,10 @@ title: ResultTypeFromSelect # Type Alias: ResultTypeFromSelect\ ```ts -type ResultTypeFromSelect = IsAny extends true ? any : WithoutRefBrand extends true ? ExtractExpressionType : TSelectObject[K] extends ToArrayWrapper ? T[] : TSelectObject[K] extends ConcatToArrayWrapper ? string : TSelectObject[K] extends QueryBuilder ? Collection> : TSelectObject[K] extends Ref ? ExtractRef<(...)[(...)]> : (...)[(...)] extends RefLeaf<(...)> ? (...) extends (...) ? (...) : (...) : (...) extends (...) ? (...) : (...) }>>; +type ResultTypeFromSelect = IsAny extends true ? any : WithoutRefBrand extends true ? ExtractExpressionType : TSelectObject[K] extends ToArrayWrapper ? T[] : TSelectObject[K] extends ConcatToArrayWrapper ? string : TSelectObject[K] extends MaterializeWrapper ? IsSingle extends true ? T | undefined : T[] : TSelectObject[K] extends { __brand: "CaseWhenWrapper"; _result?: infer T } ? ResultTypeFromCaseWhen : (...)[(...)] extends QueryBuilder<(...)> ? Collection<(...)> : (...) extends (...) ? (...) : (...) }>>; ``` -Defined in: [packages/db/src/query/builder/types.ts:309](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L309) +Defined in: [packages/db/src/query/builder/types.ts:389](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L389) ResultTypeFromSelect - Infers the result type from a select object diff --git a/docs/reference/type-aliases/SchemaFromSource.md b/docs/reference/type-aliases/SchemaFromSource.md index 35569f09df..62abaf632d 100644 --- a/docs/reference/type-aliases/SchemaFromSource.md +++ b/docs/reference/type-aliases/SchemaFromSource.md @@ -9,7 +9,7 @@ title: SchemaFromSource type SchemaFromSource = Prettify<{ [K in keyof T]: T[K] extends CollectionImpl ? InferCollectionType : T[K] extends QueryBuilder ? GetResult : never }>; ``` -Defined in: [packages/db/src/query/builder/types.ts:104](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L104) +Defined in: [packages/db/src/query/builder/types.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L116) SchemaFromSource - Converts a Source definition into a ContextSchema diff --git a/docs/reference/type-aliases/SelectObject.md b/docs/reference/type-aliases/SelectObject.md index 61ebb31249..27a738fe56 100644 --- a/docs/reference/type-aliases/SelectObject.md +++ b/docs/reference/type-aliases/SelectObject.md @@ -9,7 +9,7 @@ title: SelectObject type SelectObject = T; ``` -Defined in: [packages/db/src/query/builder/types.ts:208](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L208) +Defined in: [packages/db/src/query/builder/types.ts:279](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L279) SelectObject - Wrapper type for select clause objects diff --git a/docs/reference/type-aliases/SingleSource.md b/docs/reference/type-aliases/SingleSource.md new file mode 100644 index 0000000000..6ebceecfdb --- /dev/null +++ b/docs/reference/type-aliases/SingleSource.md @@ -0,0 +1,18 @@ +--- +id: SingleSource +title: SingleSource +--- + +# Type Alias: SingleSource\ + +```ts +type SingleSource = IsUnion extends true ? never : TSource; +``` + +Defined in: [packages/db/src/query/builder/types.ts:134](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L134) + +## Type Parameters + +### TSource + +`TSource` *extends* [`Source`](Source.md) diff --git a/docs/reference/type-aliases/Source.md b/docs/reference/type-aliases/Source.md index 9c797516a3..fe3c543014 100644 --- a/docs/reference/type-aliases/Source.md +++ b/docs/reference/type-aliases/Source.md @@ -9,15 +9,15 @@ title: Source type Source = object; ``` -Defined in: [packages/db/src/query/builder/types.ts:79](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L79) +Defined in: [packages/db/src/query/builder/types.ts:91](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L91) -Source - Input definition for query builder `from()` clause +Source - Input definition for query builder `from()` and `unionAll()` clauses Maps table aliases to either: - `CollectionImpl`: A database collection/table - `QueryBuilder`: A subquery that can be used as a table -Example: `{ users: usersCollection, orders: ordersCollection }` +Example: `{ users: usersCollection }` ## Index Signature diff --git a/docs/reference/type-aliases/SourceClauseContext.md b/docs/reference/type-aliases/SourceClauseContext.md new file mode 100644 index 0000000000..a9a4622033 --- /dev/null +++ b/docs/reference/type-aliases/SourceClauseContext.md @@ -0,0 +1,12 @@ +--- +id: SourceClauseContext +title: SourceClauseContext +--- + +# Type Alias: SourceClauseContext + +```ts +type SourceClauseContext = "from clause" | "unionAll clause" | "join clause"; +``` + +Defined in: [packages/db/src/errors.ts:388](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L388) diff --git a/docs/reference/type-aliases/StorageApi.md b/docs/reference/type-aliases/StorageApi.md index fde4dcad56..7a79766b22 100644 --- a/docs/reference/type-aliases/StorageApi.md +++ b/docs/reference/type-aliases/StorageApi.md @@ -9,6 +9,6 @@ title: StorageApi type StorageApi = Pick; ``` -Defined in: [packages/db/src/local-storage.ts:23](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L23) +Defined in: [packages/db/src/local-storage.ts:24](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L24) Storage API interface - subset of DOM Storage that we need diff --git a/docs/reference/type-aliases/StorageEventApi.md b/docs/reference/type-aliases/StorageEventApi.md index 4e3d8f10ab..de6e668685 100644 --- a/docs/reference/type-aliases/StorageEventApi.md +++ b/docs/reference/type-aliases/StorageEventApi.md @@ -9,7 +9,7 @@ title: StorageEventApi type StorageEventApi = object; ``` -Defined in: [packages/db/src/local-storage.ts:28](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L28) +Defined in: [packages/db/src/local-storage.ts:29](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L29) Storage event API - subset of Window for 'storage' events only @@ -21,7 +21,7 @@ Storage event API - subset of Window for 'storage' events only addEventListener: (type, listener) => void; ``` -Defined in: [packages/db/src/local-storage.ts:29](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L29) +Defined in: [packages/db/src/local-storage.ts:30](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L30) #### Parameters @@ -45,7 +45,7 @@ Defined in: [packages/db/src/local-storage.ts:29](https://github.com/TanStack/db removeEventListener: (type, listener) => void; ``` -Defined in: [packages/db/src/local-storage.ts:33](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L33) +Defined in: [packages/db/src/local-storage.ts:34](https://github.com/TanStack/db/blob/main/packages/db/src/local-storage.ts#L34) #### Parameters diff --git a/docs/reference/type-aliases/WhereCallback.md b/docs/reference/type-aliases/WhereCallback.md index 2f556c7215..61b99b8281 100644 --- a/docs/reference/type-aliases/WhereCallback.md +++ b/docs/reference/type-aliases/WhereCallback.md @@ -9,7 +9,7 @@ title: WhereCallback type WhereCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:129](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L129) +Defined in: [packages/db/src/query/builder/types.ts:198](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L198) WhereCallback - Type for where/having clause callback functions diff --git a/docs/reference/type-aliases/WithResult.md b/docs/reference/type-aliases/WithResult.md index 862a84cf22..e906c97ad0 100644 --- a/docs/reference/type-aliases/WithResult.md +++ b/docs/reference/type-aliases/WithResult.md @@ -9,7 +9,7 @@ title: WithResult type WithResult = Prettify & object>; ``` -Defined in: [packages/db/src/query/builder/types.ts:1034](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1034) +Defined in: [packages/db/src/query/builder/types.ts:1232](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1232) WithResult - Updates a context with a new result type after select() diff --git a/docs/reference/variables/Query.md b/docs/reference/variables/Query.md index 30e7d7ba37..4eae382bc9 100644 --- a/docs/reference/variables/Query.md +++ b/docs/reference/variables/Query.md @@ -9,4 +9,4 @@ title: Query const Query: InitialQueryBuilderConstructor = BaseQueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:1232](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1232) +Defined in: [packages/db/src/query/builder/index.ts:1408](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1408) diff --git a/docs/reference/variables/operators.md b/docs/reference/variables/operators.md index 84e9a00189..8338611985 100644 --- a/docs/reference/variables/operators.md +++ b/docs/reference/variables/operators.md @@ -6,9 +6,9 @@ title: operators # Variable: operators ```ts -const operators: readonly ["eq", "gt", "gte", "lt", "lte", "in", "like", "ilike", "and", "or", "not", "isNull", "isUndefined", "upper", "lower", "length", "concat", "add", "coalesce", "caseWhen", "count", "avg", "sum", "min", "max"]; +const operators: readonly ["eq", "gt", "gte", "lt", "lte", "in", "like", "ilike", "and", "or", "not", "isNull", "isUndefined", "upper", "lower", "length", "concat", "add", "subtract", "multiply", "divide", "coalesce", "caseWhen", "count", "avg", "sum", "min", "max"]; ``` -Defined in: [packages/db/src/query/builder/functions.ts](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts) +Defined in: [packages/db/src/query/builder/functions.ts:680](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L680) All supported operator names in TanStack DB expressions From 403be7541e86775521978c116cbea47700dd9918 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 25 Jun 2026 17:43:05 +0200 Subject: [PATCH 13/58] docs(browser-db-sqlite-persistence): document multi-tab coordinator usage (#1518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README described the package as "Phase 7 single-tab browser wiring", which suggested multi-tab was not supported. In fact, single-tab is just the default — passing a `BrowserCollectionCoordinator` via the `coordinator` option enables multi-tab coordination today. Drop the internal phase reference, list `BrowserCollectionCoordinator` in the public API, split the quick start into single-tab and multi-tab sections, and link to the offline-transactions example for the multi-tab case. Co-authored-by: Claude Opus 4.7 (1M context) --- .../browser-db-sqlite-persistence/README.md | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/browser-db-sqlite-persistence/README.md b/packages/browser-db-sqlite-persistence/README.md index b14c83b32f..84175bf7cd 100644 --- a/packages/browser-db-sqlite-persistence/README.md +++ b/packages/browser-db-sqlite-persistence/README.md @@ -1,6 +1,9 @@ # @tanstack/browser-db-sqlite-persistence -Thin browser SQLite persistence for TanStack DB using `wa-sqlite` + OPFS. +Browser SQLite persistence for TanStack DB using `wa-sqlite` + OPFS. + +Supports both single-tab (default) and multi-tab usage. Multi-tab coordination +is opt-in by passing a `BrowserCollectionCoordinator`. ## Public API @@ -8,7 +11,12 @@ Thin browser SQLite persistence for TanStack DB using `wa-sqlite` + OPFS. - `openBrowserWASQLiteOPFSDatabase(...)` - `persistedCollectionOptions(...)` (re-exported from core) -## Quick start +## Quick start (single-tab) + +By default, `createBrowserWASQLitePersistence` uses `SingleProcessCoordinator` +semantics — no leader election, no `BroadcastChannel`, no Web Locks. This is +the right choice when your app is only ever open in one tab at a time, or when +each tab uses its own database. ```ts import { createCollection } from '@tanstack/db' @@ -42,13 +50,60 @@ export const todosCollection = createCollection( ) ``` +## Multi-tab usage + +To safely share a single OPFS database across multiple tabs of the same +origin, pass a `BrowserCollectionCoordinator` via the `coordinator` option. +The coordinator uses the Web Locks API to elect a leader tab, and +`BroadcastChannel` to fan out committed transactions to follower tabs. +Follower tabs forward writes to the leader via RPC over the channel. + +```ts +import { createCollection } from '@tanstack/db' +import { + BrowserCollectionCoordinator, + createBrowserWASQLitePersistence, + openBrowserWASQLiteOPFSDatabase, + persistedCollectionOptions, +} from '@tanstack/browser-db-sqlite-persistence' + +const database = await openBrowserWASQLiteOPFSDatabase({ + databaseName: `tanstack-db.sqlite`, +}) + +const coordinator = new BrowserCollectionCoordinator({ + dbName: `tanstack-db`, +}) + +const persistence = createBrowserWASQLitePersistence({ + database, + coordinator, +}) + +export const todosCollection = createCollection( + persistedCollectionOptions({ + id: `todos`, + getKey: (todo) => todo.id, + persistence, + schemaVersion: 1, + }), +) + +// On teardown: +// coordinator.dispose() +// await database.close?.() +``` + +See [`examples/react/offline-transactions`](../../examples/react/offline-transactions/src/db/persisted-todos.ts) +for a full multi-tab example. + ## Notes -- This package is Phase 7 single-tab browser wiring: it uses - `SingleProcessCoordinator` semantics by default. - `openBrowserWASQLiteOPFSDatabase(...)` starts a dedicated Web Worker and routes SQL operations through it. OPFS sync access handle APIs are used in that worker context. -- Single-tab mode does not require BroadcastChannel or Web Locks for +- Single-tab mode does not require `BroadcastChannel` or Web Locks for correctness. +- Multi-tab mode requires `BroadcastChannel` and the Web Locks API; both are + available in all modern browsers. - OPFS capability failures are surfaced as `PersistenceUnavailableError`. From 267b36bb3d84dc3c280ed97e81e86d2fa3648dc3 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 25 Jun 2026 17:43:32 +0200 Subject: [PATCH 14/58] docs: document materialize() helper for includes subqueries (#1580) - Add a "materialize" subsection to the Includes guide in live-queries.md with array vs singleton (findOne) examples and notes on reactivity and expression-context restrictions. - Add the materialize() entry to docs/reference/index.md. The generated functions/materialize.md page is produced by the release workflow's generate-docs step, so it is not committed here. Co-authored-by: Claude Opus 4.7 (1M context) --- docs/guides/live-queries.md | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/guides/live-queries.md b/docs/guides/live-queries.md index 988628261a..4acd994d28 100644 --- a/docs/guides/live-queries.md +++ b/docs/guides/live-queries.md @@ -1252,6 +1252,48 @@ const projectsWithIssues = createLiveQueryCollection((q) => With `toArray()`, the project row is re-emitted whenever its issues change. Without it, the child `Collection` updates independently. +### materialize + +`materialize()` is a single helper that covers both multi-row and single-row includes: + +- When the wrapped subquery returns multiple rows, the parent receives `Array` — same shape as `toArray()`. +- When the wrapped subquery ends in `.findOne()`, the parent receives `T | undefined` — a single object, or `undefined` when no child matches. + +This spares callers from unwrapping a singleton array whenever they know the child query yields at most one row. Reactive semantics match `toArray()`: the parent row is re-emitted whenever the underlying children change, including insert / update / delete transitions and rows moving in or out of a match. + +```ts +import { createLiveQueryCollection, eq, materialize } from '@tanstack/db' + +// Multi-row → issues: Array +const projectsWithIssues = createLiveQueryCollection((q) => + q.from({ p: projectsCollection }).select(({ p }) => ({ + ...p, + issues: materialize( + q + .from({ i: issuesCollection }) + .where(({ i }) => eq(i.projectId, p.id)), + ), + })), +) + +// Singleton → project: Project | undefined +const issuesWithProject = createLiveQueryCollection((q) => + q.from({ i: issuesCollection }).select(({ i }) => ({ + ...i, + project: materialize( + q + .from({ p: projectsCollection }) + .where(({ p }) => eq(p.id, i.projectId)) + .findOne(), + ), + })), +) +``` + +The singleton vs. array result type is inferred from whether the wrapped query ends in `.findOne()` — no extra type annotation is required. + +Like `toArray()`, `materialize()` is only valid as a top-level value in `.select()` — it cannot be nested inside expression helpers such as `coalesce()` or `eq()`. + ### Aggregates You can use aggregate functions in child queries. Aggregates are computed per parent: From 99d33d5155520e9a364e104189f451ea567f9fb2 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Jun 2026 09:19:09 +0200 Subject: [PATCH 15/58] fix(react-db): defer eager onStoreChange to a microtask in useLiveQuery + regression test (closes #1587) (#1594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(react-db): defer eager onStoreChange to a microtask in useLiveQuery Closes #1587. `useLiveQuery`'s `subscribeRef` calls `onStoreChange()` synchronously inside the `useSyncExternalStore` subscribe function when the underlying collection is already `ready`. That synchronous notification lands during the render-to-commit window when subscribe runs under StrictMode double-render or cold/throttled loads, which React surfaces as: Can't perform a React state update on a component that hasn't mounted yet. This indicates that you have a side-effect in your render function that asynchronously tries to update the component. Move this work to useEffect instead. The fix is to defer the eager notification to a microtask so it lands after the current commit. While doing so, also guard the late notify path against an in-flight `subscribeChanges` callback firing after React unsubscribes — track a local `unsubscribed` flag and drop both the eager microtask and any in-flight subscription event after teardown, so React never sees a state update post-unsubscribe. No public API change; the contract of `useLiveQuery` is preserved (an already-ready collection still notifies React once after mount, just asynchronously instead of mid-commit). Verified `pnpm test` in packages/react-db — 94/94 pass, no type errors. Existing tests don't cover the race directly (it's a StrictMode-double-render / cold-load condition observed via Lighthouse in the issue), so the existing suite is the regression guard for existing behavior and the issue's repro is the behavioral validation. * test(react-db): add regression test for useLiveQuery eager onStoreChange (#1587) Captures the subscribe callback that useLiveQuery passes to React.useSyncExternalStore and asserts that onStoreChange is not invoked synchronously when the collection is already in the 'ready' state — it is instead deferred to a microtask. Without the fix, the eager notify lands during the render-to-commit window and React surfaces: Can't perform a React state update on a component that hasn't mounted yet. ... Move this work to useEffect instead. * chore(react-db): tighten comments around deferred onStoreChange * chore(react-db): drop #1587 reference from comment * chore(react-db): drop issue refs from eager-onStoreChange test --------- Co-authored-by: tsushanth <78000697+tsushanth@users.noreply.github.com> Co-authored-by: Kevin --- packages/react-db/src/useLiveQuery.ts | 17 ++++- .../useLiveQuery.eager-onstorechange.test.tsx | 66 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 packages/react-db/tests/useLiveQuery.eager-onstorechange.test.tsx diff --git a/packages/react-db/src/useLiveQuery.ts b/packages/react-db/src/useLiveQuery.ts index 331ff3a279..2291c2019a 100644 --- a/packages/react-db/src/useLiveQuery.ts +++ b/packages/react-db/src/useLiveQuery.ts @@ -434,17 +434,28 @@ export function useLiveQuery( return () => {} } + let unsubscribed = false + const subscription = collectionRef.current.subscribeChanges(() => { + // Drop late notifies that race with unsubscribe. + if (unsubscribed) return // Bump version on any change; getSnapshot will rebuild next time versionRef.current += 1 onStoreChange() }) - // Collection may be ready and will not receive initial `subscribeChanges()` + // Already-ready collections won't emit an initial change. Notify React + // ourselves, but defer to a microtask — calling onStoreChange synchronously + // here lands during the render-to-commit window and trips React's + // "state update on a component that hasn't mounted yet" warning. if (collectionRef.current.status === `ready`) { - versionRef.current += 1 - onStoreChange() + queueMicrotask(() => { + if (unsubscribed) return + versionRef.current += 1 + onStoreChange() + }) } return () => { + unsubscribed = true subscription.unsubscribe() } } diff --git a/packages/react-db/tests/useLiveQuery.eager-onstorechange.test.tsx b/packages/react-db/tests/useLiveQuery.eager-onstorechange.test.tsx new file mode 100644 index 0000000000..207f93156f --- /dev/null +++ b/packages/react-db/tests/useLiveQuery.eager-onstorechange.test.tsx @@ -0,0 +1,66 @@ +import { describe, expect, it, vi } from 'vitest' + +import { renderHook } from '@testing-library/react' +import { createCollection, createLiveQueryCollection } from '@tanstack/db' +import { useLiveQuery } from '../src/useLiveQuery' +import { mockSyncCollectionOptions } from '../../db/tests/utils' +import type * as ReactNS from 'react' + +// Intercept React.useSyncExternalStore so we can capture the `subscribe` +// callback that `useLiveQuery` registers and assert that it does not invoke +// `onStoreChange` synchronously when the collection is already ready. +let capturedSubscribe: ((cb: () => void) => () => void) | null = null + +vi.mock('react', async () => { + const actual = await vi.importActual('react') + return { + ...actual, + default: (actual as any).default ?? actual, + useSyncExternalStore: (subscribe: any, getSnapshot: any) => { + capturedSubscribe = subscribe + return getSnapshot() + }, + } +}) + +type Person = { id: string; name: string; age: number } + +const initialPersons: Array = [ + { id: `1`, name: `A`, age: 10 }, + { id: `2`, name: `B`, age: 20 }, +] + +describe(`useLiveQuery: eager onStoreChange must not fire synchronously during subscribe`, () => { + it(`defers the initial ready-state onStoreChange to a microtask`, async () => { + const base = createCollection( + mockSyncCollectionOptions({ + id: `eager-onstorechange-persons`, + getKey: (p) => p.id, + initialData: initialPersons, + }), + ) + + const lqc = createLiveQueryCollection({ + startSync: true, + query: (q) => q.from({ persons: base }), + }) + await lqc.preload() + expect(lqc.status).toBe(`ready`) + + capturedSubscribe = null + renderHook(() => useLiveQuery(lqc)) + expect(capturedSubscribe).toBeTypeOf(`function`) + + const onStoreChange = vi.fn() + const unsub = capturedSubscribe!(onStoreChange) + + // onStoreChange must not be invoked synchronously inside subscribe; + // it should be deferred to a microtask so it lands after React commits. + expect(onStoreChange).not.toHaveBeenCalled() + + await Promise.resolve() + expect(onStoreChange).toHaveBeenCalledTimes(1) + + unsub() + }) +}) From ac09b1177a100eafa85cba3cd09dd1f53f933ded Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Jun 2026 09:38:31 +0200 Subject: [PATCH 16/58] fix(db): prevent prototype pollution via select() alias paths (#1584) (#1595) * test: reproduce prototype pollution via select() alias (#1584) Adds a failing test demonstrating that .select() alias paths like `__proto__.polluted` or `constructor.prototype.polluted` are split on '.' and walked into the result object without sanitization, allowing prototype pollution through queryOnce(). This commit intentionally fails CI to demonstrate the vulnerability; the next commit fixes it. * fix(db): reject unsafe alias path segments in select() compiler Adds a new `UnsafeAliasPathError` (extends QueryCompilationError) and an `assertSafeAliasSegments` helper invoked in three places in packages/db/src/query/compiler/select.ts: - `addFromObject` validates each non-spread key at compile time, including dotted keys, before recording any select operation. - `processNonMergeOp` validates the split alias path before walking into the result object. - `processMerge` validates `targetPath` for the same reason. Segments matching `__proto__`, `prototype`, or `constructor` are rejected, which prevents prototype pollution via aliases like `__proto__.polluted` or `constructor.prototype.polluted` going through queryOnce() / createLiveQueryCollection(). Fixes #1584 * ci: apply automated fixes * test: address CodeRabbit review on prototype-pollution tests - Import UnsafeAliasPathError and assert that the rejection is exactly that error class instead of a permissive .rejects.toThrow(). - Drop the `({} as any).polluted` pattern in favour of Object.prototype.hasOwnProperty.call(Object.prototype, 'polluted'), which is type-safe and a more explicit assertion that Object.prototype itself was not mutated. * chore: add changeset for #1584 fix * ci: apply automated fixes * test: move prototype-pollution tests into select.test.ts Fold the select() alias prototype-pollution cases into the existing select.test.ts integration suite, reusing its createUsers() fixture, and drop the standalone select-prototype-pollution.test.ts file. Co-Authored-By: Claude Opus 4.8 (1M context) * test: drop issue number from prototype-pollution describe Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: kevin-dp Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) --- .../select-alias-prototype-pollution.md | 5 +++ packages/db/src/errors.ts | 10 +++++ packages/db/src/query/compiler/select.ts | 20 ++++++++- packages/db/tests/query/select.test.ts | 45 ++++++++++++++++++- 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 .changeset/select-alias-prototype-pollution.md diff --git a/.changeset/select-alias-prototype-pollution.md b/.changeset/select-alias-prototype-pollution.md new file mode 100644 index 0000000000..e1205ef23b --- /dev/null +++ b/.changeset/select-alias-prototype-pollution.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Fix prototype pollution via `select()` alias paths. Aliases were split on `.` and walked into the result object without sanitization, so a query like `select(() => ({ ['__proto__.polluted']: ... }))` (or any segment matching `__proto__`, `prototype`, or `constructor`) could mutate `Object.prototype`. The select compiler now rejects unsafe alias path segments with a new `UnsafeAliasPathError`. Fixes #1584. diff --git a/packages/db/src/errors.ts b/packages/db/src/errors.ts index 472fcabd12..0bfd2f9969 100644 --- a/packages/db/src/errors.ts +++ b/packages/db/src/errors.ts @@ -442,6 +442,16 @@ export class QueryCompilationError extends TanStackDBError { } } +export class UnsafeAliasPathError extends QueryCompilationError { + constructor(segment: string) { + super( + `Unsafe alias path segment "${segment}" is not allowed in .select(). ` + + `Aliases must not contain "__proto__", "prototype", or "constructor".`, + ) + this.name = `UnsafeAliasPathError` + } +} + export class DistinctRequiresSelectError extends QueryCompilationError { constructor() { super(`DISTINCT requires a SELECT clause.`) diff --git a/packages/db/src/query/compiler/select.ts b/packages/db/src/query/compiler/select.ts index e257b09ca4..88ec842915 100644 --- a/packages/db/src/query/compiler/select.ts +++ b/packages/db/src/query/compiler/select.ts @@ -5,7 +5,10 @@ import { Value as ValClass, isExpressionLike, } from '../ir.js' -import { AggregateNotSupportedError } from '../../errors.js' +import { + AggregateNotSupportedError, + UnsafeAliasPathError, +} from '../../errors.js' import { compileExpression, isCaseWhenConditionTrue } from './evaluators.js' import { containsAggregate } from './group-by.js' import type { @@ -39,6 +42,16 @@ function unwrapVal(input: any): any { return input } +const UNSAFE_ALIAS_SEGMENTS = new Set([`__proto__`, `prototype`, `constructor`]) + +function assertSafeAliasSegments(segments: ReadonlyArray): void { + for (const seg of segments) { + if (UNSAFE_ALIAS_SEGMENTS.has(seg)) { + throw new UnsafeAliasPathError(seg) + } + } +} + /** * Processes a merge operation by merging source values into the target path */ @@ -47,6 +60,7 @@ function processMerge( namespacedRow: NamespacedRow, selectResults: Record, ): void { + assertSafeAliasSegments(op.targetPath) const value = op.source(namespacedRow) if (value && typeof value === `object`) { // Ensure target object exists @@ -89,6 +103,7 @@ function processNonMergeOp( ): void { // Support nested alias paths like "meta.author.name" const path = op.alias.split(`.`) + assertSafeAliasSegments(path) if (path.length === 1) { selectResults[op.alias] = op.compiled(namespacedRow) } else { @@ -283,6 +298,9 @@ function addFromObject( ops: Array, ) { for (const [key, value] of Object.entries(obj)) { + if (!key.startsWith(`__SPREAD_SENTINEL__`)) { + assertSafeAliasSegments(key.split(`.`)) + } if (key.startsWith(`__SPREAD_SENTINEL__`)) { const rest = key.slice(`__SPREAD_SENTINEL__`.length) const splitIndex = rest.lastIndexOf(`__`) diff --git a/packages/db/tests/query/select.test.ts b/packages/db/tests/query/select.test.ts index 79dd481bb8..448936885f 100644 --- a/packages/db/tests/query/select.test.ts +++ b/packages/db/tests/query/select.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it } from 'vitest' import { createCollection } from '../../src/collection/index.js' -import { createLiveQueryCollection } from '../../src/query/index.js' +import { createLiveQueryCollection, queryOnce } from '../../src/query/index.js' +import { UnsafeAliasPathError } from '../../src/errors.js' import { mockSyncCollectionOptions } from '../utils.js' import { upper } from '../../src/query/builder/functions.js' @@ -129,3 +130,45 @@ describe(`nested select projections`, () => { }) }) }) + +function prototypeHasOwn(prop: string): boolean { + return Object.prototype.hasOwnProperty.call(Object.prototype, prop) +} + +describe(`select() alias prototype pollution`, () => { + let users: ReturnType + + beforeEach(() => { + users = createUsers() + }) + + it(`should reject __proto__ in alias path and not pollute Object.prototype`, async () => { + const hadBefore = prototypeHasOwn(`polluted`) + + await expect( + queryOnce((q) => + q.from({ user: users }).select(({ user }) => ({ + [`__proto__.polluted`]: user.name, + })), + ), + ).rejects.toThrow(UnsafeAliasPathError) + + expect(prototypeHasOwn(`polluted`)).toBe(hadBefore) + expect(prototypeHasOwn(`polluted`)).toBe(false) + }) + + it(`should reject constructor in alias path and not pollute Object.prototype`, async () => { + const hadBefore = prototypeHasOwn(`polluted`) + + await expect( + queryOnce((q) => + q.from({ user: users }).select(({ user }) => ({ + [`constructor.prototype.polluted`]: user.name, + })), + ), + ).rejects.toThrow(UnsafeAliasPathError) + + expect(prototypeHasOwn(`polluted`)).toBe(hadBefore) + expect(prototypeHasOwn(`polluted`)).toBe(false) + }) +}) From 36fb29ad7e906d39b6afdba2fd31e369c601bbb0 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Jun 2026 14:50:23 +0200 Subject: [PATCH 17/58] fix(query): lazy-join loads through the collection the join key resolves to (#1614) * test: lazy-join index warning must not blame an already-indexed collection Add a failing reproduction: when a subquery used in a JOIN clause selects its join key from the joined side of the subquery, the outer join key resolves to a collection that is already indexed. The lazy-join loader should load through that index, but today it emits a "Join requires an index" warning naming the already-indexed collection (and falls back to a full load). Data is still correct via the fallback. Co-Authored-By: Claude Opus 4.8 (1M context) * ci: apply automated fixes * test: fix BTreeIndex import path and collection typing Import BTreeIndex from src/indexes/btree-index.js (not collection/index) and type the collections via factory-wrapper ReturnType so the test passes typecheck. The index-warning assertion still fails as intended. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(query): drive lazy-join loading through the resolved collection When a subquery used in a JOIN clause selects its join key from a joined source rather than its own from clause, followRef traced the index requirement to the resolved collection while the lazy loader still subscribed to the subquery's from alias. The two diverged, producing a misleading "Join requires an index" warning that named an already-indexed collection and an unnecessary full-load fallback. followRef now also returns the alias of the source the ref resolves to; getLazyLoadTargets uses it as the subscription alias (falling back to the from-clause remapping when the key resolves directly to the from source), so lazy loading drives through the correct collection's index. Co-Authored-By: Claude Opus 4.8 (1M context) * ci: apply automated fixes --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/lazy-join-resolved-collection.md | 7 ++ .../db/src/query/compiler/lazy-targets.ts | 8 +- packages/db/src/query/ir.ts | 14 +++- packages/db/tests/query/join-subquery.test.ts | 82 ++++++++++++++++++- 4 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 .changeset/lazy-join-resolved-collection.md diff --git a/.changeset/lazy-join-resolved-collection.md b/.changeset/lazy-join-resolved-collection.md new file mode 100644 index 0000000000..de05927abe --- /dev/null +++ b/.changeset/lazy-join-resolved-collection.md @@ -0,0 +1,7 @@ +--- +'@tanstack/db': patch +--- + +fix(query): drive lazy-join loading through the collection the join key resolves to + +When a subquery used in a JOIN clause selects its join key from a _joined_ source rather than from its own `from` clause, the lazy-join loader subscribed to the wrong inner source: it used the subquery's `from` alias while computing the index requirement against the collection the key actually resolves to. This produced a misleading `Join requires an index` warning naming an already-indexed collection and an unnecessary full-load fallback. `followRef` now reports the resolved source alias, so lazy loading subscribes to the correct collection and loads through its index. diff --git a/packages/db/src/query/compiler/lazy-targets.ts b/packages/db/src/query/compiler/lazy-targets.ts index ee37c144f8..f8affd1e63 100644 --- a/packages/db/src/query/compiler/lazy-targets.ts +++ b/packages/db/src/query/compiler/lazy-targets.ts @@ -49,9 +49,15 @@ export function getLazyLoadTargets( return [] } + // The subscription we drive lazy loading through must be the one for the + // collection the join key actually resolves to. When the key traces through a + // subquery's select into a *joined* source, that collection differs from the + // subquery's from clause (which is what `aliasRemapping[lazyAlias]` yields), + // so prefer the alias reported by `followRef`. Fall back to the from-clause + // remapping when the key resolves directly to the from source. return [ { - alias: aliasRemapping[lazyAlias] || lazyAlias, + alias: followRefResult.alias || aliasRemapping[lazyAlias] || lazyAlias, collection: followRefResult.collection, path: followRefResult.path, }, diff --git a/packages/db/src/query/ir.ts b/packages/db/src/query/ir.ts index 48019a23b2..0cfb15ea50 100644 --- a/packages/db/src/query/ir.ts +++ b/packages/db/src/query/ir.ts @@ -323,13 +323,17 @@ function getRefFromAlias( /** * Follows the given reference in a query * until its finds the root field the reference points to. - * @returns The collection, its alias, and the path to the root field in this collection + * @returns The collection, its alias, and the path to the root field in this collection. + * `alias` is the alias under which the resolved collection is referenced in the + * query it was reached from (when the ref crosses into a joined source). It is + * left undefined when the ref simply resolves to a field on the passed-in + * `collection`, in which case the caller already knows the alias. */ export function followRef( query: QueryIR, ref: PropRef, collection: Collection, -): { collection: Collection; path: Array } | void { +): { collection: Collection; path: Array; alias?: string } | void { if (ref.path.length === 0) { return } @@ -365,8 +369,10 @@ export function followRef( } else { // This is a reference to a collection // we can't follow it further - // so the field must be on the collection itself - return { collection: aliasRef.collection, path: rest } + // so the field must be on the collection itself. + // Report the alias too: when the ref crossed a join, this is the source + // that actually holds the field (which may differ from the from clause). + return { collection: aliasRef.collection, path: rest, alias } } } } diff --git a/packages/db/tests/query/join-subquery.test.ts b/packages/db/tests/query/join-subquery.test.ts index 093d6e62ae..eac49d6491 100644 --- a/packages/db/tests/query/join-subquery.test.ts +++ b/packages/db/tests/query/join-subquery.test.ts @@ -1,6 +1,7 @@ -import { beforeEach, describe, expect, test } from 'vitest' +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { createLiveQueryCollection, eq, gt } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' +import { BTreeIndex } from '../../src/indexes/btree-index.js' import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js' // Sample data types for join-subquery testing @@ -873,3 +874,82 @@ describe(`Join with Subqueries`, () => { createJoinSubqueryTests(`off`) createJoinSubqueryTests(`eager`) }) + +describe(`Lazy join: subquery whose join key resolves to an indexed collection`, () => { + type Team = { id: string } + type Member = { id: string; teamId: string } + + // `teams.id` is indexed; `members` has no index. + const makeTeamsCollection = () => + createCollection( + mockSyncCollectionOptions({ + id: `lazy-join-teams`, + getKey: (r) => r.id, + autoIndex: `eager`, + defaultIndexType: BTreeIndex, + initialData: [{ id: `t1` }], + }), + ) + const makeMembersCollection = () => + createCollection( + mockSyncCollectionOptions({ + id: `lazy-join-members`, + getKey: (r) => r.id, + autoIndex: `off`, + initialData: [{ id: `m1`, teamId: `t1` }], + }), + ) + + let teams: ReturnType + let members: ReturnType + let warnSpy: ReturnType + + beforeEach(() => { + teams = makeTeamsCollection() + members = makeMembersCollection() + warnSpy = vi.spyOn(console, `warn`).mockImplementation(() => {}) + }) + + afterEach(() => { + warnSpy.mockRestore() + }) + + // When a subquery used in a JOIN clause selects its join key from the + // *joined* side of the subquery (here `team.id`) rather than from its own + // FROM side (`member`), the outer join key resolves to `teams.id`, which is + // indexed. The lazy-join loader should therefore load through that index and + // must not emit a "Join requires an index" warning that points at the + // already-indexed `teams` collection. + test(`does not warn about an index that the resolved collection already has`, () => { + const joinQuery = createLiveQueryCollection({ + startSync: true, + query: (q) => { + const teamByMember = q + .from({ member: members }) + .leftJoin({ team: teams }, ({ team, member }) => + eq(team.id, member.teamId), + ) + .select(({ team }) => ({ teamId: team.id })) + + return q + .from({ m: members }) + .leftJoin({ memberTeam: teamByMember }, ({ m, memberTeam }) => + eq(memberTeam.teamId, m.teamId), + ) + .select(({ m }) => ({ id: m.id })) + }, + }) + + // Data flows correctly regardless (via fallback full-load today). + expect(joinQuery.toArray.map((r) => r.id)).toEqual([`m1`]) + + const indexWarnings = warnSpy.mock.calls + .map((c) => String(c[0])) + .filter((m) => m.includes(`Join requires an index`)) + + // `teams.id` is already indexed, so no warning should advise indexing it. + expect(indexWarnings.filter((m) => m.includes(`lazy-join-teams`))).toEqual( + [], + ) + }) +}) From d79b0cd3fd20c1f7e2525e90121752fb6bee314c Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Jun 2026 17:14:47 +0200 Subject: [PATCH 18/58] fix: enforce all where conditions when index optimization is partial (#1582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add failing tests for index-optimized queries mixing indexed and non-indexed conditions These tests assert the expected results of currentStateAsChanges for AND/OR where clauses that combine conditions on indexed fields with conditions that cannot be served by an index. They currently fail. Co-Authored-By: Claude Fable 5 * test: add failing tests for range query boundary handling Adds expected-behaviour tests for range conditions: - compound ranges sharing a boundary value must apply the strictest bound regardless of argument order, including for date values - one-sided compound ranges must return the matching rows - strict comparisons (gt) on date fields must exclude the boundary row Co-Authored-By: Claude Fable 5 * test: add failing test for compound range query with undefined bound A compound range condition where one bound is undefined (e.g. gt(score, undefined) AND lt(score, 90)) must match nothing, since a comparison against undefined is never true. The index-optimized path must agree with a full scan. This test currently fails. Co-Authored-By: Claude Opus 4.8 (1M context) * test: add failing tests for nullish values in indexed eq/in/range queries A comparison against null/undefined is never true, but BTree indexes store and return rows with nullish indexed values (they sort as the smallest key). These tests assert that the index-optimized snapshot matches a full predicate scan for: - eq against undefined - IN with an undefined member - a range comparison over a field that has rows with undefined values - an upper-bounded compound range over such a field They currently fail. Co-Authored-By: Claude Opus 4.8 (1M context) * test: add failing tests for locale string range and NaN index queries Two more cases where an index-optimized snapshot must match a full predicate scan: - a string range predicate (e.g. name > 'z') must return a row whose value satisfies the JS relational comparison ('ö' > 'z'), even though a locale-collated index orders that value differently - eq and IN against NaN must not match a NaN-valued row, since NaN is never equal to itself They currently fail. Co-Authored-By: Claude Opus 4.8 (1M context) * test: add failing tests for range predicates over non-orderable index domains Three more cases where an index-optimized range query must match a full predicate scan: - an array-valued field (the evaluator compares with standard relational operators, which differ from the index's recursive array ordering) - a field indexed with a custom comparator - a numeric field that also contains a NaN value They currently fail. Co-Authored-By: Claude Opus 4.8 (1M context) * test: add failing tests for ordering values that have no natural order NaN and invalid Dates have no natural order. They should still get a consistent, well-defined position (alongside nulls) so that: - the comparator produces a stable total order; - ordering a collection by such a field is deterministic; - a range query on a field that contains such a value can still be served by the index rather than falling back to a full scan. These tests currently fail. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: enforce all where conditions when index optimization is partial OR expressions now require every disjunct to be index-optimizable; otherwise the query falls back to a full scan, since rows matched only by a non-optimizable disjunct cannot be recovered from index lookups. AND expressions keep partial index optimization but the optimizer now reports whether the matching keys are exact. When they are a superset (some conjuncts could not use an index, or a compound range was combined with other conditions), currentStateAsChanges re-checks each candidate row against the full where expression. Co-Authored-By: Claude Fable 5 * fix: apply strictest bound in compound range queries and fix related range edge cases Compound range conditions sharing a boundary value (gte(x,5) AND gt(x,5)) now keep the strict bound regardless of argument order. Bound values are compared with the same comparator the indexes use so dates and locale strings behave correctly. Two further issues surfaced by the regression tests: - One-sided compound ranges passed an explicit undefined bound to rangeQuery, which treats present-but-undefined as the undefined sentinel and returned an empty result. Bounds are now only passed when they exist. - BTreeIndex's exclusive lower bound check compared the normalized indexed value against the raw query value, so gt on date fields included the boundary row. It now compares against the normalized key. Co-Authored-By: Claude Fable 5 * test: add failing test for exclusive lower bound without a from bound rangeQuery with only an upper bound but fromInclusive: false must not drop the minimum key, as there is no lower bound to exclude against. This regression was introduced when the exclusive lower-bound check started comparing against the normalized fromKey (which defaults to minKey when no from bound is given). This test currently fails. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: re-filter compound range queries that use a null/undefined bound A comparison against null/undefined is never true, but in an index those values sort as the smallest key, so an index range query cannot represent such a bound. Compound range optimization now tracks selected bounds with explicit hasFromBound/hasToBound flags (separate from the bound values) and marks the result inexact when any bound value is null/undefined, so the caller re-filters against the full expression. The inexactness now also propagates through the AND combiner, which previously ignored the compound range's exactness. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: only exclude exclusive lower bound when a from bound is provided BTreeIndex.rangeQuery dropped the minimum key when called with fromInclusive: false but no from bound, because fromKey defaults to the minimum key and the exclusion check did not verify a lower bound was actually given. The exclusion is now guarded by hasFrom. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: re-filter index results that can include nullish-keyed rows BTree indexes store and return rows with a null/undefined indexed value (they sort as the smallest key), but a comparison against null/undefined is never true. The simple-comparison, IN, and compound-range optimizers now report such results as inexact so the caller re-checks candidates against the full expression: - eq/gt/gte: inexact when the query value is nullish (gt/gte with a non-null bound stay exact, since the bound excludes the bottom-sorted nullish rows) - lt/lte: conservatively inexact, as the open lower bound includes nullish-keyed rows - IN: inexact when any listed value is nullish - compound range: exact only when a non-null lower bound is present to exclude the nullish rows Co-Authored-By: Claude Opus 4.8 (1M context) * ci: apply automated fixes * fix: avoid locale string range index lookups and re-filter NaN results Two more index-optimization correctness issues: - A BTree index orders strings with localeCompare under the default 'locale' collation, but the WHERE evaluator compares strings with JS relational operators (code-point order). For range predicates these orders disagree (e.g. 'oe-umlaut' > 'z' is true in JS but sorts before 'z' under locale), so an index range lookup can omit matching rows - which re-filtering cannot recover. Locale-backed string range predicates are now left for a full scan (eq/IN use exact equality and are unaffected). - eq/IN against NaN returned isExact: true, but NaN is never equal to itself while the index still returns NaN-keyed rows (SameValueZero map equality). NaN is now treated like a nullish value for exactness, so such results are re-filtered against the full expression. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: only use indexes for range predicates when ordering is trustworthy Range optimization assumed the index orders values the same way the WHERE evaluator's relational operators do. That holds for numbers, booleans, bigints, lexical strings and valid Dates, but not for: - non-primitive operands (arrays, plain objects, Temporal, invalid Dates), which the evaluator compares via string coercion / identity while the index compares recursively; - indexes created with a custom comparator, whose order is opaque; - fields containing a NaN or invalid Date, which compare equal to every value and break the strict-weak-ordering range traversal relies on. In all three cases an index range lookup can omit genuine matches, which re-filtering cannot recover, so they now fall back to a full scan. The index exposes a supportsRangeOptimization capability (false for custom comparators or when an unorderable value is stored), and the optimizer additionally checks the operand domain. eq/IN are unaffected (exact equality, not ordering). Co-Authored-By: Claude Opus 4.8 (1M context) * ci: apply automated fixes * fix: give NaN and invalid Dates a stable sort position The comparator returned 0 for NaN against any value (and NaN from invalid-Date subtraction), so NaN had no consistent order. That made ordering by a field containing NaN non-deterministic and, worse, corrupted the strict-weak-ordering that B-tree range traversal relies on, so a stored NaN could make a range query drop genuinely matching rows. ascComparator now places NaN and invalid Dates alongside nulls, giving a well-defined total order. With a valid order the index traversal is correct again, so range queries on a field containing such values no longer deopt to a full scan: NaN simply sorts to the nulls end, where the existing exactness logic excludes it from lower-bounded ranges and re-filters it out of open-bottom (lt/lte) ranges. The index capability therefore only needs to deopt for custom comparators, so the stored-value check is removed. Co-Authored-By: Claude Opus 4.8 (1M context) * feat: adopt PostgreSQL float semantics for NaN (supersedes #1617) Fold the NaN/invalid-Date semantics from the #1617 proposal into this PR so the index-optimization correctness work is built on the final, coherent contract instead of the interim JS-semantics handling. Previously this branch gave NaN/invalid Dates a stable sort position *for sorting only* while the WHERE evaluator still rejected them (NaN != NaN), relying on re-filtering to drop index-returned NaN rows. That left a JS/SQL hybrid where joins/groupBy/distinct (which match NaN = NaN via the hash index) disagreed with WHERE/ordering. Following PostgreSQL, NaN (and invalid Dates, whose timestamp is NaN) is now equal to itself and greater than every other non-null value: - comparison.ts: ascComparator orders NaN/invalid Dates as the greatest non-null value (was: alongside nulls); isUnorderable is exported. - evaluators.ts: eq/gt/gte/lt/lte/in implement the same via valuesEqual. - index-optimization.ts: because the index and evaluator now agree on NaN/invalid Dates, they are treated as exact (no re-filter) and invalid Dates are no longer range-divergent. This resolves the reviewer's invalid-Date eq/IN issue (indexed == full-scan) and simplifies the NaN-specific defensiveness down to the remaining nullish cases. null/undefined are unchanged: still three-valued logic (UNKNOWN). Tests: new nan-semantics.test.ts (numeric NaN + invalid Date, asserting indexed == full-scan), PG-semantics comparison.test.ts and evaluator NaN block; updated the NaN tests in collection-indexes.test.ts and deterministic-ordering.test.ts that encoded the old JS behavior. Docs and a minor changeset added. Co-Authored-By: Claude Opus 4.8 (1M context) * chore: mark NaN-semantics changeset as patch (no minor before 1.0) Co-Authored-By: Claude Opus 4.8 (1M context) * test: fold nan-semantics tests into existing well-suited test files Move the unique NaN/invalid-Date coverage (lt on NaN, invalid-Date eq/IN/range index parity) into collection-indexes.test.ts alongside the existing NaN index tests, and drop the standalone nan-semantics.test.ts whose other cases were already covered by collection-indexes, deterministic-ordering and evaluators tests. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Fable 5 Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../index-optimization-partial-and-or.md | 17 + .changeset/nan-postgres-semantics.md | 15 + docs/guides/live-queries.md | 7 + packages/db/src/collection/change-events.ts | 9 +- packages/db/src/indexes/base-index.ts | 18 + packages/db/src/indexes/basic-index.ts | 1 + packages/db/src/indexes/btree-index.ts | 12 +- packages/db/src/indexes/reverse-index.ts | 4 + packages/db/src/query/compiler/evaluators.ts | 40 +- packages/db/src/utils/comparison.ts | 25 + packages/db/src/utils/index-optimization.ts | 350 +++++++++-- .../btree-index-undefined-values.test.ts | 18 + packages/db/tests/collection-indexes.test.ts | 545 ++++++++++++++++++ packages/db/tests/comparison.test.ts | 41 ++ .../db/tests/deterministic-ordering.test.ts | 33 ++ .../tests/query/compiler/evaluators.test.ts | 81 +++ 16 files changed, 1151 insertions(+), 65 deletions(-) create mode 100644 .changeset/index-optimization-partial-and-or.md create mode 100644 .changeset/nan-postgres-semantics.md create mode 100644 packages/db/tests/comparison.test.ts diff --git a/.changeset/index-optimization-partial-and-or.md b/.changeset/index-optimization-partial-and-or.md new file mode 100644 index 0000000000..b5b6067360 --- /dev/null +++ b/.changeset/index-optimization-partial-and-or.md @@ -0,0 +1,17 @@ +--- +'@tanstack/db': patch +--- + +Fix incorrect results from index-optimized `where` clauses that combine indexed and non-indexed conditions. + +- `OR` expressions are now only served from indexes when every disjunct can use an index; otherwise the query falls back to a full scan. Previously, rows matched only by a non-indexed disjunct were missing from the result. +- `AND` expressions still use indexes for the conditions that have them, but the remaining conditions are now enforced by re-checking each candidate row against the full expression. Previously, non-indexed conditions were silently dropped, returning rows that did not match the query. +- Compound range conditions (e.g. `age > 5 AND age < 10`) combined with conditions on other fields no longer ignore those other conditions. +- Compound range conditions sharing the same boundary value (e.g. `age >= 5 AND age > 5`) now apply the strictest bound regardless of the order the conditions appear in, using the same value comparison semantics as the indexes (dates, locale strings, ...). +- Compound range conditions that only bound one side (e.g. `age > 5 AND age >= 8`) no longer return an empty result. +- Strict range comparisons (`gt`/`lt`) on BTree-indexed fields holding normalized values such as dates now correctly exclude the boundary value. +- Compound range conditions with a `null`/`undefined` bound (e.g. `gt(score, undefined)`) now re-filter against the full expression instead of returning index-ordered rows, matching the semantics of a full scan (a comparison against `null`/`undefined` is never true). +- Index-optimized `eq`, `IN`, and range queries on a field that has rows with `null`/`undefined` values no longer leak those rows into results. BTree indexes store and return such rows (they sort as the smallest key), but a comparison against `null`/`undefined` is never true, so these results are now re-filtered against the full expression to stay equivalent to a full scan. +- String range conditions (`gt`/`gte`/`lt`/`lte`) on a collection using locale string collation (the default) are no longer served by the index. The index orders strings with `localeCompare` while the `where` evaluator compares them with standard relational operators, so an index range lookup could omit matching rows; these conditions now fall back to a full scan. +- Range conditions whose operand is not ordered the same way by the index and the `where` evaluator (arrays, plain objects, Temporal values) now fall back to a full scan instead of using the index, which could otherwise omit matching rows. +- Range conditions on an index created with a custom comparator now fall back to a full scan, since the comparator's ordering may not match the `where` evaluator's relational operators. diff --git a/.changeset/nan-postgres-semantics.md b/.changeset/nan-postgres-semantics.md new file mode 100644 index 0000000000..028a372523 --- /dev/null +++ b/.changeset/nan-postgres-semantics.md @@ -0,0 +1,15 @@ +--- +'@tanstack/db': patch +--- + +Adopt PostgreSQL float semantics for `NaN` in `where` clauses and ordering. + +`NaN` (and invalid `Date` values, whose timestamp is `NaN`) previously had no consistent order — `NaN === NaN` is `false` in JavaScript, so `NaN` compared unequal to everything and could not be sorted or indexed deterministically. Following PostgreSQL, `NaN` is now treated as **equal to itself** and **greater than every other non-null value**: + +- `eq(row.value, NaN)` matches rows whose value is `NaN`; `inArray(row.value, [NaN, ...])` matches them too. +- Range comparisons treat `NaN` as the greatest value: `gt`/`gte` include it, `lt`/`lte` exclude it. +- Ordering by a field containing `NaN` is now deterministic, with `NaN` sorting last (and `null` still ordered by `NULLS FIRST`/`NULLS LAST`). + +`null`/`undefined` are unaffected: they continue to use three-valued logic (a comparison with `null` yields `UNKNOWN`). + +This makes results independent of whether a query is served from an index or a full scan. diff --git a/docs/guides/live-queries.md b/docs/guides/live-queries.md index 4acd994d28..9b6ee18b06 100644 --- a/docs/guides/live-queries.md +++ b/docs/guides/live-queries.md @@ -728,6 +728,13 @@ not(condition) For a complete reference of all available functions, see the [Expression Functions Reference](#expression-functions-reference) section. +### Comparison semantics + +Comparisons follow SQL/PostgreSQL conventions rather than raw JavaScript: + +- **`null` / `undefined` use three-valued logic.** Any comparison involving `null` or `undefined` evaluates to `UNKNOWN`, so the row is not matched. For example `eq(user.score, null)` matches nothing — use a dedicated null check (e.g. `isUndefined`) to match missing values. +- **`NaN` follows PostgreSQL float semantics.** `NaN` is treated as equal to itself and greater than every other (non-null) value. So `eq(row.value, NaN)` matches `NaN` rows, `gt(row.value, x)` includes `NaN`, and ordering by such a field places `NaN` last. (Invalid `Date` values, whose timestamp is `NaN`, behave the same way.) This differs from JavaScript, where `NaN === NaN` is `false`, and matches how PostgreSQL orders and indexes floating-point values. + ## Select Use `select` to specify which fields to include in your results and transform your data. Without `select`, you get the full schema. diff --git a/packages/db/src/collection/change-events.ts b/packages/db/src/collection/change-events.ts index 6afac412ce..3f4977b7bd 100644 --- a/packages/db/src/collection/change-events.ts +++ b/packages/db/src/collection/change-events.ts @@ -138,11 +138,16 @@ export function currentStateAsChanges< ) if (optimizationResult.canOptimize) { - // Use index optimization + // Use index optimization. When the index lookup is inexact, the keys + // are a superset of the true result (some conditions could not be + // served by an index), so re-check each row against the full expression. + const filterFn = optimizationResult.isExact + ? undefined + : createFilterFunctionFromExpression(expression) const result: Array, TKey>> = [] for (const key of optimizationResult.matchingKeys) { const value = collection.get(key) - if (value !== undefined) { + if (value !== undefined && (filterFn?.(value) ?? true)) { result.push({ type: `insert`, key, diff --git a/packages/db/src/indexes/base-index.ts b/packages/db/src/indexes/base-index.ts index 4346450f90..945221e6fa 100644 --- a/packages/db/src/indexes/base-index.ts +++ b/packages/db/src/indexes/base-index.ts @@ -68,6 +68,15 @@ export interface IndexInterface< supports: (operation: IndexOperation) => boolean + /** + * Whether range lookups (gt/gte/lt/lte) on this index can be trusted to + * return every matching key. Range traversal relies on the index ordering, so + * it is unsafe when the index uses a custom comparator, whose order may not + * match the WHERE evaluator's relational operators. Callers must fall back to + * a full scan when this is `false`. + */ + get supportsRangeOptimization(): boolean + matchesField: (fieldPath: Array) => boolean matchesCompareOptions: (compareOptions: CompareOptions) => boolean matchesDirection: (direction: OrderByDirection) => boolean @@ -90,6 +99,11 @@ export abstract class BaseIndex< protected totalLookupTime = 0 protected lastUpdated = new Date() protected compareOptions: CompareOptions + /** + * Set by subclasses when constructed with a user-supplied comparator, whose + * ordering may not match the WHERE evaluator's relational operators. + */ + protected hasCustomComparator = false constructor( id: number, @@ -144,6 +158,10 @@ export abstract class BaseIndex< return this.supportedOperations.has(operation) } + get supportsRangeOptimization(): boolean { + return !this.hasCustomComparator + } + matchesField(fieldPath: Array): boolean { return ( this.expression.type === `ref` && diff --git a/packages/db/src/indexes/basic-index.ts b/packages/db/src/indexes/basic-index.ts index b80f7fb439..b9b06d1925 100644 --- a/packages/db/src/indexes/basic-index.ts +++ b/packages/db/src/indexes/basic-index.ts @@ -65,6 +65,7 @@ export class BasicIndex< ) { super(id, expression, name, options) this.compareFn = options?.compareFn ?? defaultComparator + this.hasCustomComparator = options?.compareFn != null if (options?.compareOptions) { this.compareOptions = options!.compareOptions } diff --git a/packages/db/src/indexes/btree-index.ts b/packages/db/src/indexes/btree-index.ts index 17608950a7..8b92095f01 100644 --- a/packages/db/src/indexes/btree-index.ts +++ b/packages/db/src/indexes/btree-index.ts @@ -62,6 +62,7 @@ export class BTreeIndex< // Get the base compare function const baseCompareFn = options?.compareFn ?? defaultComparator + this.hasCustomComparator = options?.compareFn != null // Wrap it to denormalize sentinels before comparison // This ensures UNDEFINED_SENTINEL is converted back to undefined @@ -247,7 +248,16 @@ export class BTreeIndex< toKey, toInclusive, (indexedValue, _) => { - if (!fromInclusive && this.compareFn(indexedValue, from) === 0) { + // Only exclude the boundary when an exclusive lower bound was + // actually provided. Without a `from` bound, `fromKey` defaults to + // the minimum key and must not be dropped. Compare against the + // normalized key since indexed values are stored normalized + // (e.g. dates as timestamps), so the raw `from` would never match. + if ( + hasFrom && + !fromInclusive && + this.compareFn(indexedValue, fromKey) === 0 + ) { // the B+ tree `forRange` method does not support exclusive lower bounds // so we need to exclude it manually return diff --git a/packages/db/src/indexes/reverse-index.ts b/packages/db/src/indexes/reverse-index.ts index 8999b2801c..6ca61636e1 100644 --- a/packages/db/src/indexes/reverse-index.ts +++ b/packages/db/src/indexes/reverse-index.ts @@ -73,6 +73,10 @@ export class ReverseIndex< return this.originalIndex.supports(operation) } + get supportsRangeOptimization(): boolean { + return this.originalIndex.supportsRangeOptimization + } + matchesField(fieldPath: Array): boolean { return this.originalIndex.matchesField(fieldPath) } diff --git a/packages/db/src/query/compiler/evaluators.ts b/packages/db/src/query/compiler/evaluators.ts index 929ac56dfb..fa2e90725d 100644 --- a/packages/db/src/query/compiler/evaluators.ts +++ b/packages/db/src/query/compiler/evaluators.ts @@ -3,7 +3,11 @@ import { UnknownExpressionTypeError, UnknownFunctionError, } from '../../errors.js' -import { areValuesEqual, normalizeValue } from '../../utils/comparison.js' +import { + areValuesEqual, + isUnorderable, + normalizeValue, +} from '../../utils/comparison.js' import type { BasicExpression, Func, PropRef } from '../ir.js' import type { NamespacedRow } from '../../types.js' @@ -14,6 +18,19 @@ function isUnknown(value: any): boolean { return value === null || value === undefined } +/** + * Equality that follows PostgreSQL float semantics for `NaN`/invalid Dates: + * such values are equal to one another and unequal to anything else. For all + * other values it defers to {@link areValuesEqual}. Operands must not be + * null/undefined (callers handle UNKNOWN first). + */ +function valuesEqual(a: any, b: any): boolean { + if (isUnorderable(a) || isUnorderable(b)) { + return isUnorderable(a) && isUnorderable(b) + } + return areValuesEqual(a, b) +} + function toDateValue(value: any): Date | null { if (value instanceof Date) { return Number.isNaN(value.getTime()) ? null : value @@ -233,8 +250,9 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any { if (isUnknown(a) || isUnknown(b)) { return null } - // Use areValuesEqual for proper Uint8Array/Buffer comparison - return areValuesEqual(a, b) + // NaN/invalid Dates are equal to one another (PostgreSQL semantics); + // otherwise use areValuesEqual for proper Uint8Array/Buffer comparison + return valuesEqual(a, b) } } case `gt`: { @@ -247,6 +265,11 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any { if (isUnknown(a) || isUnknown(b)) { return null } + // NaN/invalid Dates sort greater than every other value, and are equal + // to one another (PostgreSQL semantics) + if (isUnorderable(a) || isUnorderable(b)) { + return isUnorderable(a) && !isUnorderable(b) + } return a > b } } @@ -260,6 +283,9 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any { if (isUnknown(a) || isUnknown(b)) { return null } + if (isUnorderable(a) || isUnorderable(b)) { + return isUnorderable(a) + } return a >= b } } @@ -273,6 +299,9 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any { if (isUnknown(a) || isUnknown(b)) { return null } + if (isUnorderable(a) || isUnorderable(b)) { + return isUnorderable(b) && !isUnorderable(a) + } return a < b } } @@ -286,6 +315,9 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any { if (isUnknown(a) || isUnknown(b)) { return null } + if (isUnorderable(a) || isUnorderable(b)) { + return isUnorderable(b) + } return a <= b } } @@ -370,7 +402,7 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any { if (!Array.isArray(array)) { return false } - return array.some((item) => normalizeValue(item) === value) + return array.some((item) => valuesEqual(normalizeValue(item), value)) } } diff --git a/packages/db/src/utils/comparison.ts b/packages/db/src/utils/comparison.ts index bf5ac1a913..992f0098c5 100644 --- a/packages/db/src/utils/comparison.ts +++ b/packages/db/src/utils/comparison.ts @@ -17,6 +17,21 @@ function getObjectId(obj: object): number { return id } +/** + * Whether a value has no IEEE-754 natural order: `NaN`, or an invalid Date + * (whose timestamp is `NaN`). The query engine follows PostgreSQL float + * semantics for these values — they are all equal to one another and greater + * than every other (non-null) value — so the comparator and the WHERE + * evaluator treat them explicitly instead of letting `NaN` compare unequal to + * everything (which has no consistent order and cannot be indexed or sorted). + */ +export function isUnorderable(value: any): boolean { + return ( + (typeof value === `number` && Number.isNaN(value)) || + (value instanceof Date && Number.isNaN(value.getTime())) + ) +} + /** * Universal comparison function for all data types * Handles null/undefined, strings, arrays, dates, objects, and primitives @@ -30,6 +45,16 @@ export const ascComparator = (a: any, b: any, opts: CompareOptions): number => { if (a == null) return nulls === `first` ? -1 : 1 if (b == null) return nulls === `first` ? 1 : -1 + // Handle NaN / invalid Dates. Following PostgreSQL float semantics, they are + // all equal and sort greater than every other non-null value. This keeps the + // order total (NaN would otherwise compare equal to everything), so such + // values can be sorted and stored in tree-based indexes. + const aUnordered = isUnorderable(a) + const bUnordered = isUnorderable(b) + if (aUnordered && bUnordered) return 0 + if (aUnordered) return 1 + if (bUnordered) return -1 + // if a and b are both strings, compare them based on locale if (typeof a === `string` && typeof b === `string`) { if (opts.stringSort === `locale`) { diff --git a/packages/db/src/utils/index-optimization.ts b/packages/db/src/utils/index-optimization.ts index 81b111af56..5a52a5ec54 100644 --- a/packages/db/src/utils/index-optimization.ts +++ b/packages/db/src/utils/index-optimization.ts @@ -18,6 +18,7 @@ import { DEFAULT_COMPARE_OPTIONS } from '../utils.js' import { ReverseIndex } from '../indexes/reverse-index.js' import { hasVirtualPropPath } from '../virtual-props.js' +import { makeComparator } from './comparison.js' import type { CompareOptions } from '../query/builder/types.js' import type { IndexInterface, IndexOperation } from '../indexes/base-index.js' import type { BasicExpression } from '../query/ir.js' @@ -29,6 +30,13 @@ import type { CollectionLike } from '../types.js' export interface OptimizationResult { canOptimize: boolean matchingKeys: Set + /** + * Whether `matchingKeys` is exactly the set of keys matching the expression. + * When `false`, the keys are a superset of the true result (some conditions + * could not be served by an index) and each row must be re-checked against + * the full expression before being included in the result. + */ + isExact: boolean } /** @@ -94,6 +102,94 @@ export function unionSets(sets: Array>): Set { return result } +/** + * Whether a value can be matched exactly by an index lookup, i.e. the index + * result for it is not a superset that the caller must re-filter. + * + * Only `null`/`undefined` are inexact: the WHERE evaluator's three-valued logic + * makes any comparison against them UNKNOWN, yet a BTree index stores and + * returns rows with nullish keys (they sort to the nulls end), so a result that + * could include such rows must be re-filtered. + * + * `NaN` and invalid Dates are exact: under the engine's PostgreSQL float + * semantics they are equal to themselves and ordered (greatest non-null value), + * so the evaluator and the index agree on them and no re-filtering is needed. + */ +function isExactComparisonValue(value: unknown): boolean { + return value != null +} + +/** + * Whether the collection orders strings using locale collation. + * + * Under `stringSort: 'locale'` a BTree string index orders values with + * `localeCompare`, but the WHERE evaluator compares strings with JS relational + * operators (code-point order). For range predicates these orders disagree + * (e.g. `'ö' > 'z'` is true in JS but `'ö'` sorts before `'z'` under locale + * `en`), so an index range lookup can omit matching rows. Such omissions cannot + * be recovered by re-filtering, so locale-backed string range predicates must + * not be index-optimized. + */ +function usesLocaleStringSort(collection: CollectionLike): boolean { + const opts = { ...DEFAULT_COMPARE_OPTIONS, ...collection.compareOptions } + return opts.stringSort === `locale` +} + +/** + * Whether a range predicate on this operand would use an index ordering that + * differs from the WHERE evaluator's relational operators, so an index range + * lookup could omit genuine matches that re-filtering cannot recover. + * + * The evaluator compares with JS relational operators (extended with + * PostgreSQL float semantics for `NaN`/invalid Dates). That order matches the + * index comparator for numbers, booleans, bigints, lexically-sorted strings, + * Dates (valid, ordered by time; invalid, ordered as the greatest value) and + * `NaN`. It diverges for locale-sorted strings (localeCompare vs code-point + * order) and for arrays, plain objects, Temporal values and typed arrays + * (recursive/identity ordering vs string coercion). + * + * Note: `null`/`undefined` operands are not handled here — those are superset + * cases handled by re-filtering ({@link isExactComparisonValue}). + */ +function isRangeOrderingDivergent( + value: unknown, + collection: CollectionLike, +): boolean { + switch (typeof value) { + case `number`: + case `bigint`: + case `boolean`: + return false + case `string`: + return usesLocaleStringSort(collection) + case `object`: { + if (value === null) return false + // Dates order consistently with the evaluator: valid Dates by time, and + // invalid Dates as the greatest value under PostgreSQL float semantics. + return !(value instanceof Date) + } + default: + return false + } +} + +/** + * Whether a range predicate (gt/gte/lt/lte) on this operand can be safely + * served by the given index: the operand's domain must order the same way the + * index does, and the index itself must support trustworthy range traversal + * (no custom comparator). + */ +function canRangeOptimize( + value: unknown, + index: IndexInterface, + collection: CollectionLike, +): boolean { + return ( + !isRangeOrderingDivergent(value, collection) && + index.supportsRangeOptimization + ) +} + /** * Optimizes a query expression using available indexes to find matching keys */ @@ -134,7 +230,7 @@ function optimizeQueryRecursive( } } - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } /** @@ -167,6 +263,14 @@ export function canOptimizeExpression< return false } +/** + * Result of compound range optimization, including which AND arguments + * were covered by the range query so the caller can process the rest. + */ +interface CompoundRangeResult extends OptimizationResult { + coveredArgIndices: Set +} + /** * Optimizes compound range queries on the same field * Example: WHERE age > 5 AND age < 10 @@ -177,9 +281,14 @@ function optimizeCompoundRangeQuery< >( expression: BasicExpression, collection: CollectionLike, -): OptimizationResult { +): CompoundRangeResult { if (expression.type !== `func` || expression.args.length < 2) { - return { canOptimize: false, matchingKeys: new Set() } + return { + canOptimize: false, + matchingKeys: new Set(), + isExact: false, + coveredArgIndices: new Set(), + } } // Group range operations by field @@ -188,11 +297,12 @@ function optimizeCompoundRangeQuery< Array<{ operation: `gt` | `gte` | `lt` | `lte` value: any + argIndex: number }> >() // Collect all range operations from AND arguments - for (const arg of expression.args) { + for (const [argIndex, arg] of expression.args.entries()) { if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) { const rangeOp = arg as any if (rangeOp.args.length === 2) { @@ -238,7 +348,7 @@ function optimizeCompoundRangeQuery< if (!fieldOperations.has(fieldKey)) { fieldOperations.set(fieldKey, []) } - fieldOperations.get(fieldKey)!.push({ operation, value }) + fieldOperations.get(fieldKey)!.push({ operation, value, argIndex }) } } } @@ -250,55 +360,114 @@ function optimizeCompoundRangeQuery< const fieldPath = fieldKey.split(`.`) const index = findIndexForField(collection, fieldPath) + // Only collapse this field into a range query when every bound's domain + // orders the same way the index does and the index supports trustworthy + // range traversal. Otherwise the index may omit matching rows that + // re-filtering cannot recover, so leave the field for a full scan. + if ( + index && + operations.some((op) => !canRangeOptimize(op.value, index, collection)) + ) { + continue + } + if (index && index.supports(`gt`) && index.supports(`lt`)) { - // Build range query options + // Compare values with the same semantics the index uses (dates, + // locale strings, ...), in ascending order since bounds are about + // value order regardless of the index direction + const compare = makeComparator({ + ...DEFAULT_COMPARE_OPTIONS, + ...collection.compareOptions, + direction: `asc`, + }) + + // Build range query options, keeping the strictest bound on each + // side: a larger lower bound (or smaller upper bound) wins, and at + // equal values the exclusive operation wins over the inclusive one. + // `hasFromBound`/`hasToBound` track whether a bound was selected, + // separately from the bound value (which may legitimately be falsy). let from: any = undefined let to: any = undefined + let hasFromBound = false + let hasToBound = false let fromInclusive = true let toInclusive = true + // A comparison against null/undefined is never true, but in an index + // nullish values sort to the nulls end, so a range query cannot + // represent such a bound. Track it and force a re-filter instead of + // claiming the result is exact. (NaN/invalid Dates are ordered and + // comparable under PostgreSQL semantics, so they are real bounds.) + let hasNonComparableBound = false for (const { operation, value } of operations) { + if (!isExactComparisonValue(value)) { + hasNonComparableBound = true + continue + } switch (operation) { case `gt`: - if (from === undefined || value > from) { + case `gte`: { + const cmp = hasFromBound ? compare(value, from) : 1 + if (cmp > 0) { from = value + hasFromBound = true + fromInclusive = operation === `gte` + } else if (cmp === 0 && operation === `gt`) { fromInclusive = false } break - case `gte`: - if (from === undefined || value > from) { - from = value - fromInclusive = true - } - break + } case `lt`: - if (to === undefined || value < to) { + case `lte`: { + const cmp = hasToBound ? compare(value, to) : -1 + if (cmp < 0) { to = value + hasToBound = true + toInclusive = operation === `lte` + } else if (cmp === 0 && operation === `lt`) { toInclusive = false } break - case `lte`: - if (to === undefined || value < to) { - to = value - toInclusive = true - } - break + } } } - const matchingKeys = (index as any).rangeQuery({ - from, - to, - fromInclusive, - toInclusive, - }) - - return { canOptimize: true, matchingKeys } + // Only pass the bounds that were selected: rangeQuery distinguishes + // an absent bound (open-ended) from an explicitly provided one + const rangeOptions: Record = {} + if (hasFromBound) { + rangeOptions.from = from + rangeOptions.fromInclusive = fromInclusive + } + if (hasToBound) { + rangeOptions.to = to + rangeOptions.toInclusive = toInclusive + } + const matchingKeys = (index as any).rangeQuery(rangeOptions) + + return { + canOptimize: true, + matchingKeys, + // The range result is exact only when it cannot include rows with a + // nullish indexed value (which a comparison would reject but the + // index returns, as they sort as the smallest key). That requires a + // non-nullish lower bound to exclude them: without `hasFromBound` + // the range is open at the bottom and captures those rows, and a + // non-comparable bound value (`hasNonComparableBound`) can never + // bound them out. + isExact: hasFromBound && !hasNonComparableBound, + coveredArgIndices: new Set(operations.map((op) => op.argIndex)), + } } } } - return { canOptimize: false, matchingKeys: new Set() } + return { + canOptimize: false, + matchingKeys: new Set(), + isExact: false, + coveredArgIndices: new Set(), + } } /** @@ -312,7 +481,7 @@ function optimizeSimpleComparison< collection: CollectionLike, ): OptimizationResult { if (expression.type !== `func` || expression.args.length !== 2) { - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } const leftArg = expression.args[0]! @@ -362,15 +531,46 @@ function optimizeSimpleComparison< // Check if the index supports this operation if (!index.supports(indexOperation)) { - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } + } + + // A range op can only use the index when the operand's domain orders the + // same way the index does and the index supports trustworthy traversal. + // Otherwise the index may omit matching rows, which re-filtering cannot + // recover, so fall back to a full scan. + if ( + (operation === `gt` || + operation === `gte` || + operation === `lt` || + operation === `lte`) && + !canRangeOptimize(queryValue, index, collection) + ) { + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } const matchingKeys = index.lookup(indexOperation, queryValue) - return { canOptimize: true, matchingKeys } + + // A comparison against a nullish value is never true, but BTree indexes + // store and return rows with nullish keys (they sort to the nulls end). + // Determine whether the index result is exact or a superset that the + // caller must re-filter: + // - eq/gt/gte: a nullish query value matches nothing while the index + // still returns nullish-keyed rows -> inexact. A non-nullish lower + // bound (gt/gte) excludes those bottom-sorted rows, so they stay exact. + // - lt/lte: the open lower bound always includes nullish-keyed rows, + // so the result is conservatively inexact. + // NaN/invalid Dates are exact here: under PostgreSQL float semantics the + // evaluator and the index agree on them (equal to self, greatest). + const isExact = + operation === `lt` || operation === `lte` + ? false + : isExactComparisonValue(queryValue) + + return { canOptimize: true, matchingKeys, isExact } } } - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } /** @@ -412,22 +612,41 @@ function optimizeAndExpression( collection: CollectionLike, ): OptimizationResult { if (expression.type !== `func` || expression.args.length < 2) { - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } // First, try to optimize compound range queries on the same field + // (e.g. age > 5 AND age < 10 becomes a single range query) const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection) - if (compoundRangeResult.canOptimize) { - return compoundRangeResult - } + const coveredArgIndices = compoundRangeResult.canOptimize + ? compoundRangeResult.coveredArgIndices + : new Set() const results: Array> = [] + if (compoundRangeResult.canOptimize) { + results.push(compoundRangeResult) + } - // Try to optimize each part, keep the optimizable ones - for (const arg of expression.args) { + // Try to optimize the remaining conjuncts, keep the optimizable ones. + // Conjuncts that cannot use an index make the result inexact: the + // intersection is then a superset of the true result and must be + // re-filtered against the full expression by the caller. The compound + // range result may itself be inexact (e.g. a null/undefined bound). + let allConjunctsExact = !compoundRangeResult.canOptimize + ? true + : compoundRangeResult.isExact + for (const [argIndex, arg] of expression.args.entries()) { + if (coveredArgIndices.has(argIndex)) { + continue + } const result = optimizeQueryRecursive(arg, collection) if (result.canOptimize) { results.push(result) + if (!result.isExact) { + allConjunctsExact = false + } + } else { + allConjunctsExact = false } } @@ -435,10 +654,14 @@ function optimizeAndExpression( // Use intersectSets utility for AND logic const allMatchingSets = results.map((r) => r.matchingKeys) const intersectedKeys = intersectSets(allMatchingSets) - return { canOptimize: true, matchingKeys: intersectedKeys } + return { + canOptimize: true, + matchingKeys: intersectedKeys, + isExact: allConjunctsExact, + } } - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } /** @@ -464,27 +687,31 @@ function optimizeOrExpression( collection: CollectionLike, ): OptimizationResult { if (expression.type !== `func` || expression.args.length < 2) { - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } const results: Array> = [] - // Try to optimize each part, keep the optimizable ones + // Every disjunct must be optimizable: rows matched only by a disjunct + // that cannot use an index would be missing from the union, and no + // post-filtering can recover them. In that case fall back to a full scan. for (const arg of expression.args) { const result = optimizeQueryRecursive(arg, collection) - if (result.canOptimize) { - results.push(result) + if (!result.canOptimize) { + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } + results.push(result) } - if (results.length > 0) { - // Use unionSets utility for OR logic - const allMatchingSets = results.map((r) => r.matchingKeys) - const unionedKeys = unionSets(allMatchingSets) - return { canOptimize: true, matchingKeys: unionedKeys } + // Use unionSets utility for OR logic + const allMatchingSets = results.map((r) => r.matchingKeys) + const unionedKeys = unionSets(allMatchingSets) + return { + canOptimize: true, + matchingKeys: unionedKeys, + // An inexact (superset) disjunct makes the union a superset as well + isExact: results.every((r) => r.isExact), } - - return { canOptimize: false, matchingKeys: new Set() } } /** @@ -498,8 +725,9 @@ function canOptimizeOrExpression< return false } - // If any argument can be optimized, we can gain some speedup - return expression.args.some((arg) => canOptimizeExpression(arg, collection)) + // Every disjunct must be optimizable, otherwise the union would miss + // rows matched only by the non-optimizable disjuncts + return expression.args.every((arg) => canOptimizeExpression(arg, collection)) } /** @@ -513,7 +741,7 @@ function optimizeInArrayExpression< collection: CollectionLike, ): OptimizationResult { if (expression.type !== `func` || expression.args.length !== 2) { - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } const fieldArg = expression.args[0]! @@ -528,11 +756,17 @@ function optimizeInArrayExpression< const values = (arrayArg as any).value const index = findIndexForField(collection, fieldPath) + // A nullish or NaN member can never be matched by `IN` (a comparison + // against null/undefined/NaN is never true), but the index would still + // return rows with such an indexed value. When the list contains one of + // those the result is a superset that the caller must re-filter. + const isExact = values.every((value: any) => isExactComparisonValue(value)) + if (index) { // Check if the index supports IN operation if (index.supports(`in`)) { const matchingKeys = index.lookup(`in`, values) - return { canOptimize: true, matchingKeys } + return { canOptimize: true, matchingKeys, isExact } } else if (index.supports(`eq`)) { // Fallback to multiple equality lookups const matchingKeys = new Set() @@ -542,12 +776,12 @@ function optimizeInArrayExpression< matchingKeys.add(key) } } - return { canOptimize: true, matchingKeys } + return { canOptimize: true, matchingKeys, isExact } } } } - return { canOptimize: false, matchingKeys: new Set() } + return { canOptimize: false, matchingKeys: new Set(), isExact: false } } /** diff --git a/packages/db/tests/btree-index-undefined-values.test.ts b/packages/db/tests/btree-index-undefined-values.test.ts index 11e29690c6..1510c02e3c 100644 --- a/packages/db/tests/btree-index-undefined-values.test.ts +++ b/packages/db/tests/btree-index-undefined-values.test.ts @@ -248,6 +248,24 @@ describe(`BTreeIndex - undefined value handling`, () => { expect(withoutFrom.size).toBe(3) }) + it(`should not drop the minimum key when an upper-only range is exclusive on the (absent) lower bound`, () => { + // When no `from` bound is provided, `fromInclusive` must not cause the + // smallest key to be excluded: there is no lower bound to exclude + // against. Only an explicitly provided exclusive lower bound should + // drop its boundary value. + const index = createIndex(`value`) + index.add(`a`, { value: 1 }) + index.add(`b`, { value: 5 }) + index.add(`c`, { value: 10 }) + + const result = index.rangeQuery({ to: 10, fromInclusive: false }) + + expect(result.size).toBe(3) + expect(result).toContain(`a`) + expect(result).toContain(`b`) + expect(result).toContain(`c`) + }) + it(`should handle range query from undefined to undefined`, () => { const index = createIndex(`value`) index.add(`a`, { value: undefined }) diff --git a/packages/db/tests/collection-indexes.test.ts b/packages/db/tests/collection-indexes.test.ts index a441a5520d..bd5f4868c7 100644 --- a/packages/db/tests/collection-indexes.test.ts +++ b/packages/db/tests/collection-indexes.test.ts @@ -625,6 +625,19 @@ describe(`Collection Indexes`, () => { }) }) + it(`should exclude the boundary value from greater than queries on dates`, () => { + // gt must be strict for date fields: Bob was created exactly on + // 2023-01-02, so only rows created strictly later may be returned. + collection.createIndex((row) => row.createdAt) + + const result = collection.currentStateAsChanges({ + where: gt(new PropRef([`createdAt`]), new Date(`2023-01-02`)), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Charlie`, `Diana`, `Eve`]) + }) + it(`should perform greater than or equal queries`, () => { withIndexTracking(collection, (tracker) => { const result = collection.currentStateAsChanges({ @@ -1179,6 +1192,538 @@ describe(`Collection Indexes`, () => { }) }) }) + + it(`should include rows matched by any OR condition when conditions mix indexed and non-indexed expressions`, () => { + // An OR query must return the union of rows matching each condition: + // eq(age, 25) matches Alice (age 25) + // gt(length(name), 6) matches Charlie (name length 7) + // `age` has an index while `length(name)` is a computed expression + // without one, but the chosen execution strategy must not change the + // result: both Alice and Charlie satisfy the OR and must be returned. + const result = collection.currentStateAsChanges({ + where: or( + eq(new PropRef([`age`]), 25), + gt(length(new PropRef([`name`])), 6), + ), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Alice`, `Charlie`]) + }) + + it(`should only return rows matching every AND condition when conditions mix indexed and non-indexed expressions`, () => { + // An AND query must return only the rows matching all conditions: + // eq(status, 'active') matches Alice, Charlie and Eve + // gt(length(name), 6) matches only Charlie (name length 7) + // `status` has an index while `length(name)` is a computed expression + // without one, but every condition must still be enforced: only + // Charlie satisfies both. + const result = collection.currentStateAsChanges({ + where: and( + eq(new PropRef([`status`]), `active`), + gt(length(new PropRef([`name`])), 6), + ), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Charlie`]) + }) + + it(`should apply the strictest lower bound when range conditions share the same value`, () => { + // gte(age, 25) AND gt(age, 25) reduces to age > 25: the strict + // comparison wins at the shared boundary, so Alice (age 25) must be + // excluded regardless of the order the conditions appear in. + const result = collection.currentStateAsChanges({ + where: and(gte(new PropRef([`age`]), 25), gt(new PropRef([`age`]), 25)), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Bob`, `Charlie`, `Diana`]) + }) + + it(`should apply the strictest upper bound when range conditions share the same value`, () => { + // lte(age, 30) AND lt(age, 30) reduces to age < 30: the strict + // comparison wins at the shared boundary, so Bob (age 30) must be + // excluded. + const result = collection.currentStateAsChanges({ + where: and(lte(new PropRef([`age`]), 30), lt(new PropRef([`age`]), 30)), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Alice`, `Diana`, `Eve`]) + }) + + it(`should apply the strictest bound for date ranges sharing the same value`, () => { + // Distinct Date instances representing the same point in time must be + // treated as equal values: gte(createdAt, jan2) AND gt(createdAt, jan2) + // reduces to createdAt > jan2, so Bob (created 2023-01-02) must be + // excluded. + collection.createIndex((row) => row.createdAt) + + const result = collection.currentStateAsChanges({ + where: and( + gte(new PropRef([`createdAt`]), new Date(`2023-01-02`)), + gt(new PropRef([`createdAt`]), new Date(`2023-01-02`)), + ), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Charlie`, `Diana`, `Eve`]) + }) + + it(`should enforce every AND condition when a range on one field is combined with conditions on other fields`, () => { + // An AND query that contains a compound range on one field plus a + // condition on another field must enforce all of them: + // gt(age, 24) AND lt(age, 36) matches Alice (25), Bob (30), + // Charlie (35) and Diana (28) + // eq(status, 'active') matches Alice, Charlie and Eve + // Only Alice and Charlie satisfy the full conjunction. + const result = collection.currentStateAsChanges({ + where: and( + gt(new PropRef([`age`]), 24), + lt(new PropRef([`age`]), 36), + eq(new PropRef([`status`]), `active`), + ), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Alice`, `Charlie`]) + }) + + it(`should match a full scan when a range condition uses an undefined bound`, () => { + // A comparison against `undefined` matches no rows (a comparison with + // null/undefined is never true), so `gt(score, undefined)` excludes + // every row and the whole AND must return nothing. The index-optimized + // path must agree with a plain full scan and not leak rows. + collection.createIndex((row) => row.score) + + const result = collection.currentStateAsChanges({ + where: and( + gt(new PropRef([`score`]), undefined), + lt(new PropRef([`score`]), 90), + ), + })! + + expect(result).toEqual([]) + }) + + it(`should not match rows with a missing value for an equality on undefined`, () => { + // An equality comparison against `undefined` is never true, so + // `eq(score, undefined)` must return no rows even though Eve has an + // undefined score. The index-optimized path must agree with a full + // predicate scan. + collection.createIndex((row) => row.score) + + const result = collection.currentStateAsChanges({ + where: eq(new PropRef([`score`]), undefined), + })! + + expect(result).toEqual([]) + }) + + it(`should ignore an undefined member when matching an IN list`, () => { + // A row only matches `IN` when its value equals one of the listed + // values; a comparison with `undefined` is never true. So + // `inArray(score, [undefined, 80])` must match only Bob (score 80) + // and must not match Eve (undefined score). + collection.createIndex((row) => row.score) + + const result = collection.currentStateAsChanges({ + where: inArray(new PropRef([`score`]), [undefined, 80]), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Bob`]) + }) + + it(`should not match rows with a missing value for a range comparison`, () => { + // A range comparison against a row with an undefined value is never + // true, so `lt(score, 85)` must match only Bob (score 80) and must + // not match Eve (undefined score). + collection.createIndex((row) => row.score) + + const result = collection.currentStateAsChanges({ + where: lt(new PropRef([`score`]), 85), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Bob`]) + }) + + it(`should not match rows with a missing value for an upper-bounded compound range`, () => { + // A compound range with only upper bounds (e.g. score <= 90) must not + // match a row with an undefined value, since a comparison against + // undefined is never true. Only Bob (80), Charlie (90) and Diana (85) + // satisfy `score <= 90`; Eve (undefined) must be excluded. + collection.createIndex((row) => row.score) + + const result = collection.currentStateAsChanges({ + where: and( + lte(new PropRef([`score`]), 90), + lte(new PropRef([`score`]), 95), + ), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`Bob`, `Charlie`, `Diana`]) + }) + + it(`should match a string range predicate using the same ordering as a full scan`, async () => { + // String comparisons in the WHERE evaluator use JS relational operators + // (code-point order), where `'ö' > 'z'` is true. A row named `ö` must + // therefore be returned by `name > 'z'`, even though a locale-collated + // index orders `ö` before `z`. The index-optimized result must agree + // with a full predicate scan. + const stringCollection = createCollection< + { id: string; name: string }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `eager`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `1`, name: `apple` } }) + write({ type: `insert`, value: { id: `2`, name: `ö` } }) + commit() + markReady() + }, + }, + }) + await stringCollection.stateWhenReady() + stringCollection.createIndex((row) => row.name) + + const result = stringCollection.currentStateAsChanges({ + where: gt(new PropRef([`name`]), `z`), + })! + + const names = result.map((r) => r.value.name).sort() + expect(names).toEqual([`ö`]) + }) + + it(`should match a row with a NaN value for an equality on NaN`, async () => { + // Under PostgreSQL float semantics NaN is equal to itself, so + // `eq(score, NaN)` matches the NaN-valued row (and the index, which + // stores and returns it, agrees with a full scan). + const nanCollection = createCollection< + { id: string; score: number }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `eager`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `1`, score: 5 } }) + write({ type: `insert`, value: { id: `2`, score: NaN } }) + commit() + markReady() + }, + }, + }) + await nanCollection.stateWhenReady() + nanCollection.createIndex((row) => row.score) + + const result = nanCollection.currentStateAsChanges({ + where: eq(new PropRef([`score`]), NaN), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`2`]) + }) + + it(`should match a row with a NaN value for an IN list containing NaN`, async () => { + // A row matches `IN` when its value equals a listed value. Under + // PostgreSQL float semantics NaN is equal to itself, so + // `inArray(score, [NaN, 5])` matches both the score-5 row and the + // NaN-valued row. + const nanCollection = createCollection< + { id: string; score: number }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `eager`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `1`, score: 5 } }) + write({ type: `insert`, value: { id: `2`, score: NaN } }) + commit() + markReady() + }, + }, + }) + await nanCollection.stateWhenReady() + nanCollection.createIndex((row) => row.score) + + const result = nanCollection.currentStateAsChanges({ + where: inArray(new PropRef([`score`]), [NaN, 5]), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`1`, `2`]) + }) + + it(`should return array-valued rows for a range predicate consistently with a full scan`, async () => { + // Range predicates are evaluated with standard relational comparison, + // under which `[2] > [10]` is true (arrays compare as their string + // form). An index on an array-valued field must return the same rows as + // a full scan and must not drop this match. + const arrayCollection = createCollection< + { id: string; value: Array }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `off`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `1`, value: [2] } }) + commit() + markReady() + }, + }, + }) + await arrayCollection.stateWhenReady() + arrayCollection.createIndex((row) => row.value) + + const result = arrayCollection.currentStateAsChanges({ + where: gt(new PropRef([`value`]), [10]), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`1`]) + }) + + it(`should return all matching rows for a range predicate on a custom-comparator index`, async () => { + // A range predicate must return every row that satisfies it regardless + // of the comparator the index was created with. With scores 5 and 20, + // `score > 10` matches only the row with score 20. + const customCollection = createCollection< + { id: string; score: number }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `off`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `low`, score: 5 } }) + write({ type: `insert`, value: { id: `high`, score: 20 } }) + commit() + markReady() + }, + }, + }) + await customCollection.stateWhenReady() + customCollection.createIndex((row) => row.score, { + options: { compareFn: (a: number, b: number) => b - a }, + }) + + const result = customCollection.currentStateAsChanges({ + where: gt(new PropRef([`score`]), 10), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`high`]) + }) + + it(`should return all matching rows for a range predicate when the field also contains NaN`, async () => { + // A range predicate must return every matching row even when other rows + // hold a NaN value for the field. Under PostgreSQL float semantics NaN is + // the greatest value, so with scores NaN, 1, 3, 5 and 7, `score > 2` + // matches the rows with scores 3, 5, 7 and NaN. + const nanCollection = createCollection< + { id: string; score: number }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `off`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `nan`, score: NaN } }) + write({ type: `insert`, value: { id: `one`, score: 1 } }) + write({ type: `insert`, value: { id: `three`, score: 3 } }) + write({ type: `insert`, value: { id: `five`, score: 5 } }) + write({ type: `insert`, value: { id: `seven`, score: 7 } }) + commit() + markReady() + }, + }, + }) + await nanCollection.stateWhenReady() + nanCollection.createIndex((row) => row.score) + + const result = nanCollection.currentStateAsChanges({ + where: gt(new PropRef([`score`]), 2), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`five`, `nan`, `seven`, `three`]) + }) + + it(`should use the index for a range query on a field that also contains NaN`, async () => { + // A NaN value has a well-defined sort position (greatest, under + // PostgreSQL float semantics), so a range query on the field can still be + // served by the index and does not need to fall back to a full scan. + const nanCollection = createCollection< + { id: string; score: number }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `off`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `nan`, score: NaN } }) + write({ type: `insert`, value: { id: `one`, score: 1 } }) + write({ type: `insert`, value: { id: `three`, score: 3 } }) + write({ type: `insert`, value: { id: `five`, score: 5 } }) + write({ type: `insert`, value: { id: `seven`, score: 7 } }) + commit() + markReady() + }, + }, + }) + await nanCollection.stateWhenReady() + nanCollection.createIndex((row) => row.score) + + withIndexTracking(nanCollection, (tracker) => { + const result = nanCollection.currentStateAsChanges({ + where: gt(new PropRef([`score`]), 2), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`five`, `nan`, `seven`, `three`]) + + expectIndexUsage(tracker.stats, { + shouldUseIndex: true, + shouldUseFullScan: false, + }) + }) + }) + + it(`should exclude NaN from a less-than range query`, async () => { + // Under PostgreSQL float semantics NaN is the greatest value, so + // `score < 4` matches the rows with scores 1 and 3 but never the + // NaN-valued row. + const nanCollection = createCollection< + { id: string; score: number }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `off`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: `insert`, value: { id: `nan`, score: NaN } }) + write({ type: `insert`, value: { id: `one`, score: 1 } }) + write({ type: `insert`, value: { id: `three`, score: 3 } }) + write({ type: `insert`, value: { id: `five`, score: 5 } }) + write({ type: `insert`, value: { id: `seven`, score: 7 } }) + commit() + markReady() + }, + }, + }) + await nanCollection.stateWhenReady() + nanCollection.createIndex((row) => row.score) + + const result = nanCollection.currentStateAsChanges({ + where: lt(new PropRef([`score`]), 4), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`one`, `three`]) + }) + + // Invalid Dates have a NaN timestamp, so they follow the same PostgreSQL + // float semantics as NaN: equal to one another and greater than every valid + // Date. The index-served and full-scan results must agree. + const makeInvalidDateCollection = async () => { + const dateCollection = createCollection< + { id: string; createdAt: Date }, + string + >({ + getKey: (row) => row.id, + startSync: true, + autoIndex: `off`, + defaultIndexType: BTreeIndex, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ + type: `insert`, + value: { id: `invalid`, createdAt: new Date(`not a date`) }, + }) + write({ + type: `insert`, + value: { id: `valid`, createdAt: new Date(`2023-01-01`) }, + }) + commit() + markReady() + }, + }, + }) + await dateCollection.stateWhenReady() + dateCollection.createIndex((row) => row.createdAt) + return dateCollection + } + + it(`should match an invalid-Date row for an equality on an invalid Date`, async () => { + const dateCollection = await makeInvalidDateCollection() + + const result = dateCollection.currentStateAsChanges({ + where: eq(new PropRef([`createdAt`]), new Date(`not a date`)), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`invalid`]) + }) + + it(`should match an invalid-Date member of an IN list`, async () => { + const dateCollection = await makeInvalidDateCollection() + + const result = dateCollection.currentStateAsChanges({ + where: inArray(new PropRef([`createdAt`]), [ + new Date(`not a date`), + new Date(`2023-01-01`), + ]), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`invalid`, `valid`]) + }) + + it(`should treat an invalid Date as greater than valid Dates in a range query`, async () => { + // `createdAt > 2022` matches the valid Date and the invalid Date (which + // is the greatest value under PostgreSQL float semantics). + const dateCollection = await makeInvalidDateCollection() + + const result = dateCollection.currentStateAsChanges({ + where: gt(new PropRef([`createdAt`]), new Date(`2022-01-01`)), + })! + + const ids = result.map((r) => r.value.id).sort() + expect(ids).toEqual([`invalid`, `valid`]) + }) }) describe(`Index Usage Verification`, () => { diff --git a/packages/db/tests/comparison.test.ts b/packages/db/tests/comparison.test.ts new file mode 100644 index 0000000000..ae40488f43 --- /dev/null +++ b/packages/db/tests/comparison.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest' +import { ascComparator, defaultComparator } from '../src/utils/comparison' +import { DEFAULT_COMPARE_OPTIONS } from '../src/utils' + +describe(`ascComparator - PostgreSQL float semantics for NaN`, () => { + const opts = DEFAULT_COMPARE_OPTIONS // nulls: `first` + + it(`orders NaN greater than every number`, () => { + expect(ascComparator(NaN, 5, opts)).toBeGreaterThan(0) + expect(ascComparator(5, NaN, opts)).toBeLessThan(0) + }) + + it(`treats NaN as equal to NaN`, () => { + expect(ascComparator(NaN, NaN, opts)).toBe(0) + }) + + it(`produces a stable total order with NaN sorting last`, () => { + const sorted = [3, NaN, 1, 5, NaN].sort((a, b) => defaultComparator(a, b)) + + expect(sorted.slice(0, 3)).toEqual([1, 3, 5]) + expect(sorted.slice(3).every((v) => Number.isNaN(v))).toBe(true) + }) + + it(`keeps null before non-null values regardless of NaN`, () => { + // nulls still sort first by default; NaN sorts last (greatest non-null) + const sorted = [5, NaN, null, 1].sort((a, b) => defaultComparator(a, b)) + + expect(sorted[0]).toBe(null) + expect(sorted[1]).toBe(1) + expect(sorted[2]).toBe(5) + expect(Number.isNaN(sorted[3])).toBe(true) + }) + + it(`orders an invalid Date greater than valid Dates`, () => { + const invalid = new Date(`not a date`) + const valid = new Date(`2023-01-01`) + + expect(ascComparator(invalid, valid, opts)).toBeGreaterThan(0) + expect(ascComparator(valid, invalid, opts)).toBeLessThan(0) + }) +}) diff --git a/packages/db/tests/deterministic-ordering.test.ts b/packages/db/tests/deterministic-ordering.test.ts index 9ce9a326d6..160d03284b 100644 --- a/packages/db/tests/deterministic-ordering.test.ts +++ b/packages/db/tests/deterministic-ordering.test.ts @@ -489,5 +489,38 @@ describe(`Deterministic Ordering`, () => { const keys = changes?.map((c) => c.key) expect(keys).toEqual([`a`, `b`, `c`]) }) + + it(`should place NaN values consistently when ordering`, () => { + type Item = { id: string; score: number } + + const options = mockSyncCollectionOptions({ + id: `test-collection-changes-nan`, + getKey: (item) => item.id, + initialData: [], + }) + + const collection = createCollection(options) + + options.utils.begin() + options.utils.write({ type: `insert`, value: { id: `a`, score: 5 } }) + options.utils.write({ type: `insert`, value: { id: `nan`, score: NaN } }) + options.utils.write({ type: `insert`, value: { id: `b`, score: 1 } }) + options.utils.write({ type: `insert`, value: { id: `c`, score: 3 } }) + options.utils.commit() + + const changes = collection.currentStateAsChanges({ + orderBy: [ + { + expression: new PropRef([`score`]), + compareOptions: { direction: `asc`, nulls: `first` }, + }, + ], + }) + + // Under PostgreSQL float semantics NaN is the greatest value, so the + // numbers sort ascending first and NaN sorts last. + const keys = changes?.map((c) => c.key) + expect(keys).toEqual([`b`, `c`, `a`, `nan`]) + }) }) }) diff --git a/packages/db/tests/query/compiler/evaluators.test.ts b/packages/db/tests/query/compiler/evaluators.test.ts index 69969de18a..4c5acb78c1 100644 --- a/packages/db/tests/query/compiler/evaluators.test.ts +++ b/packages/db/tests/query/compiler/evaluators.test.ts @@ -730,6 +730,87 @@ describe(`evaluators`, () => { expect(compiled({})).toBe(null) }) }) + + describe(`NaN (PostgreSQL float semantics)`, () => { + // Following PostgreSQL, NaN is equal to itself and greater than every + // other (non-null) value, so it has a well-defined order. + it(`treats NaN as equal to NaN`, () => { + const func = new Func(`eq`, [new Value(NaN), new Value(NaN)]) + expect(compileExpression(func)({})).toBe(true) + }) + + it(`treats NaN as not equal to a number`, () => { + const func = new Func(`eq`, [new Value(NaN), new Value(5)]) + expect(compileExpression(func)({})).toBe(false) + }) + + it(`still returns UNKNOWN when comparing NaN with null`, () => { + const func = new Func(`eq`, [new Value(NaN), new Value(null)]) + expect(compileExpression(func)({})).toBe(null) + }) + + it(`treats NaN as greater than every number`, () => { + expect( + compileExpression(new Func(`gt`, [new Value(NaN), new Value(5)]))( + {}, + ), + ).toBe(true) + expect( + compileExpression(new Func(`gt`, [new Value(5), new Value(NaN)]))( + {}, + ), + ).toBe(false) + expect( + compileExpression( + new Func(`gt`, [new Value(NaN), new Value(NaN)]), + )({}), + ).toBe(false) + }) + + it(`orders NaN with gte/lt/lte consistently`, () => { + // NaN >= anything (including NaN); nothing finite >= NaN + expect( + compileExpression( + new Func(`gte`, [new Value(NaN), new Value(5)]), + )({}), + ).toBe(true) + expect( + compileExpression( + new Func(`gte`, [new Value(NaN), new Value(NaN)]), + )({}), + ).toBe(true) + // NaN < nothing; a finite value < NaN + expect( + compileExpression(new Func(`lt`, [new Value(NaN), new Value(5)]))( + {}, + ), + ).toBe(false) + expect( + compileExpression(new Func(`lt`, [new Value(5), new Value(NaN)]))( + {}, + ), + ).toBe(true) + // NaN <= NaN; a finite value <= NaN + expect( + compileExpression( + new Func(`lte`, [new Value(NaN), new Value(NaN)]), + )({}), + ).toBe(true) + expect( + compileExpression( + new Func(`lte`, [new Value(5), new Value(NaN)]), + )({}), + ).toBe(true) + }) + + it(`matches NaN inside an IN list`, () => { + const func = new Func(`in`, [ + new Value(NaN), + new Value([NaN, 1, 2]), + ]) + expect(compileExpression(func)({})).toBe(true) + }) + }) }) describe(`boolean operators`, () => { From b05139f7159e47f67e01036ddc7235f819cf0e2c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:44:00 +0200 Subject: [PATCH 19/58] ci: Version Packages (#1621) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../index-optimization-partial-and-or.md | 17 ----- .changeset/lazy-join-resolved-collection.md | 7 -- .changeset/nan-postgres-semantics.md | 15 ---- .../select-alias-prototype-pollution.md | 5 -- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 7 ++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 34 ++++++++++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 62 files changed, 290 insertions(+), 136 deletions(-) delete mode 100644 .changeset/index-optimization-partial-and-or.md delete mode 100644 .changeset/lazy-join-resolved-collection.md delete mode 100644 .changeset/nan-postgres-semantics.md delete mode 100644 .changeset/select-alias-prototype-pollution.md diff --git a/.changeset/index-optimization-partial-and-or.md b/.changeset/index-optimization-partial-and-or.md deleted file mode 100644 index b5b6067360..0000000000 --- a/.changeset/index-optimization-partial-and-or.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Fix incorrect results from index-optimized `where` clauses that combine indexed and non-indexed conditions. - -- `OR` expressions are now only served from indexes when every disjunct can use an index; otherwise the query falls back to a full scan. Previously, rows matched only by a non-indexed disjunct were missing from the result. -- `AND` expressions still use indexes for the conditions that have them, but the remaining conditions are now enforced by re-checking each candidate row against the full expression. Previously, non-indexed conditions were silently dropped, returning rows that did not match the query. -- Compound range conditions (e.g. `age > 5 AND age < 10`) combined with conditions on other fields no longer ignore those other conditions. -- Compound range conditions sharing the same boundary value (e.g. `age >= 5 AND age > 5`) now apply the strictest bound regardless of the order the conditions appear in, using the same value comparison semantics as the indexes (dates, locale strings, ...). -- Compound range conditions that only bound one side (e.g. `age > 5 AND age >= 8`) no longer return an empty result. -- Strict range comparisons (`gt`/`lt`) on BTree-indexed fields holding normalized values such as dates now correctly exclude the boundary value. -- Compound range conditions with a `null`/`undefined` bound (e.g. `gt(score, undefined)`) now re-filter against the full expression instead of returning index-ordered rows, matching the semantics of a full scan (a comparison against `null`/`undefined` is never true). -- Index-optimized `eq`, `IN`, and range queries on a field that has rows with `null`/`undefined` values no longer leak those rows into results. BTree indexes store and return such rows (they sort as the smallest key), but a comparison against `null`/`undefined` is never true, so these results are now re-filtered against the full expression to stay equivalent to a full scan. -- String range conditions (`gt`/`gte`/`lt`/`lte`) on a collection using locale string collation (the default) are no longer served by the index. The index orders strings with `localeCompare` while the `where` evaluator compares them with standard relational operators, so an index range lookup could omit matching rows; these conditions now fall back to a full scan. -- Range conditions whose operand is not ordered the same way by the index and the `where` evaluator (arrays, plain objects, Temporal values) now fall back to a full scan instead of using the index, which could otherwise omit matching rows. -- Range conditions on an index created with a custom comparator now fall back to a full scan, since the comparator's ordering may not match the `where` evaluator's relational operators. diff --git a/.changeset/lazy-join-resolved-collection.md b/.changeset/lazy-join-resolved-collection.md deleted file mode 100644 index de05927abe..0000000000 --- a/.changeset/lazy-join-resolved-collection.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/db': patch ---- - -fix(query): drive lazy-join loading through the collection the join key resolves to - -When a subquery used in a JOIN clause selects its join key from a _joined_ source rather than from its own `from` clause, the lazy-join loader subscribed to the wrong inner source: it used the subquery's `from` alias while computing the index requirement against the collection the key actually resolves to. This produced a misleading `Join requires an index` warning naming an already-indexed collection and an unnecessary full-load fallback. `followRef` now reports the resolved source alias, so lazy loading subscribes to the correct collection and loads through its index. diff --git a/.changeset/nan-postgres-semantics.md b/.changeset/nan-postgres-semantics.md deleted file mode 100644 index 028a372523..0000000000 --- a/.changeset/nan-postgres-semantics.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Adopt PostgreSQL float semantics for `NaN` in `where` clauses and ordering. - -`NaN` (and invalid `Date` values, whose timestamp is `NaN`) previously had no consistent order — `NaN === NaN` is `false` in JavaScript, so `NaN` compared unequal to everything and could not be sorted or indexed deterministically. Following PostgreSQL, `NaN` is now treated as **equal to itself** and **greater than every other non-null value**: - -- `eq(row.value, NaN)` matches rows whose value is `NaN`; `inArray(row.value, [NaN, ...])` matches them too. -- Range comparisons treat `NaN` as the greatest value: `gt`/`gte` include it, `lt`/`lte` exclude it. -- Ordering by a field containing `NaN` is now deterministic, with `NaN` sorting last (and `null` still ordered by `NULLS FIRST`/`NULLS LAST`). - -`null`/`undefined` are unaffected: they continue to use three-valued logic (a comparison with `null` yields `UNKNOWN`). - -This makes results independent of whether a query is served from an index or a full scan. diff --git a/.changeset/select-alias-prototype-pollution.md b/.changeset/select-alias-prototype-pollution.md deleted file mode 100644 index e1205ef23b..0000000000 --- a/.changeset/select-alias-prototype-pollution.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Fix prototype pollution via `select()` alias paths. Aliases were split on `.` and walked into the result object without sanitization, so a query like `select(() => ({ ['__proto__.polluted']: ... }))` (or any segment matching `__proto__`, `prototype`, or `constructor`) could mutate `Object.prototype`. The select compiler now rejects unsafe alias path segments with a new `UnsafeAliasPathError`. Fixes #1584. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index 7829800350..e4bec9fd77 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.70", - "@tanstack/db": "^0.6.10", + "@tanstack/angular-db": "^0.1.71", + "@tanstack/db": "^0.6.11", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index 2ba2aea097..dc638b0491 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.14", - "@tanstack/node-db-sqlite-persistence": "^0.2.2", - "@tanstack/offline-transactions": "^1.0.35", - "@tanstack/query-db-collection": "^1.0.42", - "@tanstack/react-db": "^0.1.88", + "@tanstack/electron-db-sqlite-persistence": "^0.1.15", + "@tanstack/node-db-sqlite-persistence": "^0.2.3", + "@tanstack/offline-transactions": "^1.0.36", + "@tanstack/query-db-collection": "^1.0.43", + "@tanstack/react-db": "^0.1.89", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index a8ec7a0635..b73e0de5ef 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.10", - "@tanstack/offline-transactions": "^1.0.35", - "@tanstack/query-db-collection": "^1.0.42", - "@tanstack/react-db": "^0.1.88", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.2", + "@tanstack/db": "^0.6.11", + "@tanstack/offline-transactions": "^1.0.36", + "@tanstack/query-db-collection": "^1.0.43", + "@tanstack/react-db": "^0.1.89", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.3", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 8b4fee8599..251e4fb0ee 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.10", - "@tanstack/electric-db-collection": "^0.3.8", - "@tanstack/offline-transactions": "^1.0.35", - "@tanstack/react-db": "^0.1.88", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.2", + "@tanstack/db": "^0.6.11", + "@tanstack/electric-db-collection": "^0.3.9", + "@tanstack/offline-transactions": "^1.0.36", + "@tanstack/react-db": "^0.1.89", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.3", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index 212d0c0a7a..dbd42fe890 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.2", - "@tanstack/db": "^0.6.10", - "@tanstack/offline-transactions": "^1.0.35", - "@tanstack/query-db-collection": "^1.0.42", - "@tanstack/react-db": "^0.1.88", + "@tanstack/browser-db-sqlite-persistence": "^0.2.3", + "@tanstack/db": "^0.6.11", + "@tanstack/offline-transactions": "^1.0.36", + "@tanstack/query-db-collection": "^1.0.43", + "@tanstack/react-db": "^0.1.89", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index c9ea092585..bbba118c04 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.10", - "@tanstack/react-db": "^0.1.88", + "@tanstack/db": "^0.6.11", + "@tanstack/react-db": "^0.1.89", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 837e2f371b..6d9ebf69a3 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.42", - "@tanstack/react-db": "^0.1.88", + "@tanstack/query-db-collection": "^1.0.43", + "@tanstack/react-db": "^0.1.89", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index dc7365451e..c50c4e8f48 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.8", + "@tanstack/electric-db-collection": "^0.3.9", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.42", - "@tanstack/react-db": "^0.1.88", + "@tanstack/query-db-collection": "^1.0.43", + "@tanstack/react-db": "^0.1.89", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.88", + "@tanstack/trailbase-db-collection": "^0.1.89", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index e19558c2e6..160da016d8 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.8", + "@tanstack/electric-db-collection": "^0.3.9", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.42", - "@tanstack/solid-db": "^0.2.24", + "@tanstack/query-db-collection": "^1.0.43", + "@tanstack/solid-db": "^0.2.25", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.88", + "@tanstack/trailbase-db-collection": "^0.1.89", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 09961d8f45..379f8cc4ac 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.71 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.1.70 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 509fd65f04..860c95cc10 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.70", + "version": "0.1.71", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index a7963d8ac7..96b233451a 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.3 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index b57349c6c8..13c5f72813 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.2", + "version": "0.2.3", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 2c19dc8950..919e68651d 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.3 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index f9809c5dec..731515350c 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.15 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + - @tanstack/capacitor-db-sqlite-persistence@0.2.3 + ## 0.0.14 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index f180809b89..14cfbdb8c5 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.14", + "version": "0.0.15", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 171b52bf5b..065d73caf5 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.2", + "version": "0.2.3", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index 3aacab134c..9450a149fb 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.3 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 500030ec02..dac189a3a0 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.2", + "version": "0.2.3", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index 3a1c664490..55dedf5638 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.3 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.2.2 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 26cdd105c4..6a13001545 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.2", + "version": "0.2.3", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index a6574da80f..241da16fc4 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,39 @@ # @tanstack/db +## 0.6.11 + +### Patch Changes + +- Fix incorrect results from index-optimized `where` clauses that combine indexed and non-indexed conditions. ([#1582](https://github.com/TanStack/db/pull/1582)) + - `OR` expressions are now only served from indexes when every disjunct can use an index; otherwise the query falls back to a full scan. Previously, rows matched only by a non-indexed disjunct were missing from the result. + - `AND` expressions still use indexes for the conditions that have them, but the remaining conditions are now enforced by re-checking each candidate row against the full expression. Previously, non-indexed conditions were silently dropped, returning rows that did not match the query. + - Compound range conditions (e.g. `age > 5 AND age < 10`) combined with conditions on other fields no longer ignore those other conditions. + - Compound range conditions sharing the same boundary value (e.g. `age >= 5 AND age > 5`) now apply the strictest bound regardless of the order the conditions appear in, using the same value comparison semantics as the indexes (dates, locale strings, ...). + - Compound range conditions that only bound one side (e.g. `age > 5 AND age >= 8`) no longer return an empty result. + - Strict range comparisons (`gt`/`lt`) on BTree-indexed fields holding normalized values such as dates now correctly exclude the boundary value. + - Compound range conditions with a `null`/`undefined` bound (e.g. `gt(score, undefined)`) now re-filter against the full expression instead of returning index-ordered rows, matching the semantics of a full scan (a comparison against `null`/`undefined` is never true). + - Index-optimized `eq`, `IN`, and range queries on a field that has rows with `null`/`undefined` values no longer leak those rows into results. BTree indexes store and return such rows (they sort as the smallest key), but a comparison against `null`/`undefined` is never true, so these results are now re-filtered against the full expression to stay equivalent to a full scan. + - String range conditions (`gt`/`gte`/`lt`/`lte`) on a collection using locale string collation (the default) are no longer served by the index. The index orders strings with `localeCompare` while the `where` evaluator compares them with standard relational operators, so an index range lookup could omit matching rows; these conditions now fall back to a full scan. + - Range conditions whose operand is not ordered the same way by the index and the `where` evaluator (arrays, plain objects, Temporal values) now fall back to a full scan instead of using the index, which could otherwise omit matching rows. + - Range conditions on an index created with a custom comparator now fall back to a full scan, since the comparator's ordering may not match the `where` evaluator's relational operators. + +- fix(query): drive lazy-join loading through the collection the join key resolves to ([#1614](https://github.com/TanStack/db/pull/1614)) + + When a subquery used in a JOIN clause selects its join key from a _joined_ source rather than from its own `from` clause, the lazy-join loader subscribed to the wrong inner source: it used the subquery's `from` alias while computing the index requirement against the collection the key actually resolves to. This produced a misleading `Join requires an index` warning naming an already-indexed collection and an unnecessary full-load fallback. `followRef` now reports the resolved source alias, so lazy loading subscribes to the correct collection and loads through its index. + +- Adopt PostgreSQL float semantics for `NaN` in `where` clauses and ordering. ([#1582](https://github.com/TanStack/db/pull/1582)) + + `NaN` (and invalid `Date` values, whose timestamp is `NaN`) previously had no consistent order — `NaN === NaN` is `false` in JavaScript, so `NaN` compared unequal to everything and could not be sorted or indexed deterministically. Following PostgreSQL, `NaN` is now treated as **equal to itself** and **greater than every other non-null value**: + - `eq(row.value, NaN)` matches rows whose value is `NaN`; `inArray(row.value, [NaN, ...])` matches them too. + - Range comparisons treat `NaN` as the greatest value: `gt`/`gte` include it, `lt`/`lte` exclude it. + - Ordering by a field containing `NaN` is now deterministic, with `NaN` sorting last (and `null` still ordered by `NULLS FIRST`/`NULLS LAST`). + + `null`/`undefined` are unaffected: they continue to use three-valued logic (a comparison with `null` yields `UNKNOWN`). + + This makes results independent of whether a query is served from an index or a full scan. + +- Fix prototype pollution via `select()` alias paths. Aliases were split on `.` and walked into the result object without sanitization, so a query like `select(() => ({ ['__proto__.polluted']: ... }))` (or any segment matching `__proto__`, `prototype`, or `constructor`) could mutate `Object.prototype`. The select compiler now rejects unsafe alias path segments with a new `UnsafeAliasPathError`. Fixes #1584. ([#1595](https://github.com/TanStack/db/pull/1595)) + ## 0.6.10 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 03d84b67ea..bb52641e7a 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.10", + "version": "0.6.11", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 7d02c6ec29..89b05da7e1 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.9 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.3.8 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index 9c03109450..1d216becd7 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.8", + "version": "0.3.9", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 7bac5e1b85..c362e9eaed 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.15 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.1.14 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 80d7b57dd5..72fb5e29e5 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.14", + "version": "0.1.15", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index 68fa4658c9..129d9c443f 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.3 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index 80f5d686f6..b013055f00 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.15 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + - @tanstack/expo-db-sqlite-persistence@0.2.3 + ## 0.0.14 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index fb7d4c2fe2..8932b4e515 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.14", + "version": "0.0.15", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 9e71f67794..f2e6e22f35 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.2", + "version": "0.2.3", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index f685d49021..f6a0fd5ed2 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.3 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index ce5eefc2e9..d82a133a5f 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.2", + "version": "0.2.3", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index dbb4255300..6c538f8830 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.36 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 1.0.35 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index c336d46380..7f1e7e93d4 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.35", + "version": "1.0.36", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index a5c28761ad..ad6f924787 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.49 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.1.48 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 39466dc011..d2d0a4bf20 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.48", + "version": "0.1.49", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index 3e868617c0..cfde88b398 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.43 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 1.0.42 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index fc58d3dbc8..331682899f 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.42", + "version": "1.0.43", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index 2322d676c9..a49980d0d5 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.89 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.1.88 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 7b7ac675c3..e4633ffcda 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.88", + "version": "0.1.89", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 7029e671b8..61aa61d6d5 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.3 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index f42bfa63ce..f2eadb562a 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.2", + "version": "0.2.3", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 21cac83f26..3c65a86f03 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.77 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.1.76 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index 98530d438c..3c601b0893 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.76", + "version": "0.1.77", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index a9ef5cc2f4..5fd08df702 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.25 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.2.24 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index dbaa187d57..f69f9d54ec 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.24", + "version": "0.2.25", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 45333b0511..ee482232a0 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.88 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.1.87 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index c3c7b292ae..35f3baeaa4 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.87", + "version": "0.1.88", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 97893f5f26..14a5883d5f 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.3 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 287bbd79a3..1dc4e1415e 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.15 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + - @tanstack/tauri-db-sqlite-persistence@0.2.3 + ## 0.0.14 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index a5c17377ed..0acaf00182 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.14", + "version": "0.0.15", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index 1be0c86c24..dfc5a6b9de 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.2", + "version": "0.2.3", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index f631d325cd..25015c330e 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.89 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.1.88 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 7403bb4587..8a321eaa64 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.88", + "version": "0.1.89", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 2e559852d4..be960b9861 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.122 + +### Patch Changes + +- Updated dependencies [[`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`36fb29a`](https://github.com/TanStack/db/commit/36fb29ad7e906d39b6afdba2fd31e369c601bbb0), [`d79b0cd`](https://github.com/TanStack/db/commit/d79b0cd3fd20c1f7e2525e90121752fb6bee314c), [`ac09b11`](https://github.com/TanStack/db/commit/ac09b1177a100eafa85cba3cd09dd1f53f933ded)]: + - @tanstack/db@0.6.11 + ## 0.0.121 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index fbec37e9ab..1bc53c538e 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.121", + "version": "0.0.122", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 525d66dab4..0d45f72454 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.70 + specifier: ^0.1.71 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.10 + specifier: ^0.6.11 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.14 + specifier: ^0.1.15 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.2 + specifier: ^0.2.3 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.35 + specifier: ^1.0.36 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.42 + specifier: ^1.0.43 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.10 + specifier: ^0.6.11 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.35 + specifier: ^1.0.36 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.42 + specifier: ^1.0.43 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.2 + specifier: ^0.2.3 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.10 + specifier: ^0.6.11 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.8 + specifier: ^0.3.9 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.35 + specifier: ^1.0.36 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.2 + specifier: ^0.2.3 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.2 + specifier: ^0.2.3 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.10 + specifier: ^0.6.11 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.35 + specifier: ^1.0.36 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.42 + specifier: ^1.0.43 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.10 + specifier: ^0.6.11 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.42 + specifier: ^1.0.43 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.8 + specifier: ^0.3.9 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.42 + specifier: ^1.0.43 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.8 + specifier: ^0.3.9 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.42 + specifier: ^1.0.43 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.24 + specifier: ^0.2.25 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.88 + specifier: ^0.1.89 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From e6a8cc262c89f2c502499f6a12f87460639c6bd4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:01:25 -0600 Subject: [PATCH 20/58] docs: regenerate API documentation (#1622) Co-authored-by: github-actions[bot] --- .../namespaces/IR/functions/followRef.md | 10 +- .../AggregateFunctionNotInSelectError.md | 4 +- .../classes/AggregateNotSupportedError.md | 4 +- docs/reference/classes/BTreeIndex.md | 113 ++++++--- docs/reference/classes/BaseIndex.md | 115 ++++++--- docs/reference/classes/BasicIndex.md | 113 ++++++--- .../CannotCombineEmptyExpressionListError.md | 4 +- .../classes/CollectionInputNotFoundError.md | 4 +- .../classes/DistinctRequiresSelectError.md | 4 +- .../classes/DuplicateAliasInSubqueryError.md | 4 +- .../classes/EmptyReferencePathError.md | 4 +- .../classes/FnSelectWithGroupByError.md | 4 +- docs/reference/classes/GroupByError.md | 4 +- .../classes/HavingRequiresGroupByError.md | 4 +- .../reference/classes/InvalidJoinCondition.md | 4 +- .../InvalidJoinConditionLeftSourceError.md | 4 +- .../InvalidJoinConditionRightSourceError.md | 4 +- .../InvalidJoinConditionSameSourceError.md | 4 +- ...InvalidJoinConditionSourceMismatchError.md | 4 +- .../classes/InvalidStorageDataFormatError.md | 4 +- .../InvalidStorageObjectFormatError.md | 4 +- .../classes/JoinCollectionNotFoundError.md | 4 +- docs/reference/classes/JoinError.md | 4 +- .../classes/LimitOffsetRequireOrderByError.md | 4 +- .../classes/LocalStorageCollectionError.md | 4 +- .../classes/MissingAliasInputsError.md | 4 +- ...NonAggregateExpressionNotInGroupByError.md | 4 +- .../classes/QueryCompilationError.md | 1 + docs/reference/classes/QueryOptimizerError.md | 4 +- docs/reference/classes/ReverseIndex.md | 54 +++-- docs/reference/classes/SerializationError.md | 4 +- .../classes/SetWindowRequiresOrderByError.md | 4 +- docs/reference/classes/StorageError.md | 4 +- .../classes/StorageKeyRequiredError.md | 4 +- .../classes/SubscriptionNotFoundError.md | 4 +- docs/reference/classes/SyncCleanupError.md | 4 +- .../classes/UnknownExpressionTypeError.md | 4 +- .../reference/classes/UnknownFunctionError.md | 4 +- .../UnknownHavingExpressionTypeError.md | 4 +- .../reference/classes/UnsafeAliasPathError.md | 220 ++++++++++++++++++ .../UnsupportedAggregateFunctionError.md | 4 +- .../classes/UnsupportedFromTypeError.md | 4 +- .../classes/UnsupportedJoinSourceTypeError.md | 4 +- .../classes/UnsupportedJoinTypeError.md | 4 +- .../UnsupportedRootScalarSelectError.md | 4 +- .../classes/WhereClauseConversionError.md | 4 +- docs/reference/functions/compileExpression.md | 2 +- .../functions/compileSingleRowExpression.md | 2 +- docs/reference/functions/findIndexForField.md | 2 +- .../optimizeExpressionWithIndexes.md | 2 +- .../reference/functions/toBooleanPredicate.md | 2 +- docs/reference/index.md | 1 + docs/reference/interfaces/IndexInterface.md | 30 ++- .../type-aliases/IndexConstructor.md | 2 +- 54 files changed, 613 insertions(+), 212 deletions(-) create mode 100644 docs/reference/classes/UnsafeAliasPathError.md diff --git a/docs/reference/@tanstack/namespaces/IR/functions/followRef.md b/docs/reference/@tanstack/namespaces/IR/functions/followRef.md index 4b356998f3..ed666f655a 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/followRef.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/followRef.md @@ -12,12 +12,13 @@ function followRef( collection): | void | { + alias?: string; collection: Collection; path: string[]; }; ``` -Defined in: [packages/db/src/query/ir.ts:328](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L328) +Defined in: [packages/db/src/query/ir.ts:332](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L332) Follows the given reference in a query until its finds the root field the reference points to. @@ -40,8 +41,13 @@ until its finds the root field the reference points to. \| `void` \| \{ + `alias?`: `string`; `collection`: [`Collection`](../../../../interfaces/Collection.md); `path`: `string`[]; \} -The collection, its alias, and the path to the root field in this collection +The collection, its alias, and the path to the root field in this collection. +`alias` is the alias under which the resolved collection is referenced in the +query it was reached from (when the ref crosses into a joined source). It is +left undefined when the ref simply resolves to a field on the passed-in +`collection`, in which case the caller already knows the alias. diff --git a/docs/reference/classes/AggregateFunctionNotInSelectError.md b/docs/reference/classes/AggregateFunctionNotInSelectError.md index 0cbd5ea3cb..506a12bf7a 100644 --- a/docs/reference/classes/AggregateFunctionNotInSelectError.md +++ b/docs/reference/classes/AggregateFunctionNotInSelectError.md @@ -5,7 +5,7 @@ title: AggregateFunctionNotInSelectError # Class: AggregateFunctionNotInSelectError -Defined in: [packages/db/src/errors.ts:629](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L629) +Defined in: [packages/db/src/errors.ts:639](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L639) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:629](https://github.com/TanStack/db/blob/ new AggregateFunctionNotInSelectError(functionName): AggregateFunctionNotInSelectError; ``` -Defined in: [packages/db/src/errors.ts:630](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L630) +Defined in: [packages/db/src/errors.ts:640](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L640) #### Parameters diff --git a/docs/reference/classes/AggregateNotSupportedError.md b/docs/reference/classes/AggregateNotSupportedError.md index 739ade6c1a..b5807bbe10 100644 --- a/docs/reference/classes/AggregateNotSupportedError.md +++ b/docs/reference/classes/AggregateNotSupportedError.md @@ -5,7 +5,7 @@ title: AggregateNotSupportedError # Class: AggregateNotSupportedError -Defined in: [packages/db/src/errors.ts:745](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L745) +Defined in: [packages/db/src/errors.ts:755](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L755) Error thrown when aggregate expressions are used outside of a GROUP BY context. @@ -21,7 +21,7 @@ Error thrown when aggregate expressions are used outside of a GROUP BY context. new AggregateNotSupportedError(): AggregateNotSupportedError; ``` -Defined in: [packages/db/src/errors.ts:746](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L746) +Defined in: [packages/db/src/errors.ts:756](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L756) #### Returns diff --git a/docs/reference/classes/BTreeIndex.md b/docs/reference/classes/BTreeIndex.md index 3072cef868..bd7bdd6a96 100644 --- a/docs/reference/classes/BTreeIndex.md +++ b/docs/reference/classes/BTreeIndex.md @@ -68,7 +68,7 @@ Defined in: [packages/db/src/indexes/btree-index.ts:55](https://github.com/TanSt protected compareOptions: CompareOptions; ``` -Defined in: [packages/db/src/indexes/base-index.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L92) +Defined in: [packages/db/src/indexes/base-index.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L101) #### Inherited from @@ -82,7 +82,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:92](https://github.com/TanSta readonly expression: BasicExpression; ``` -Defined in: [packages/db/src/indexes/base-index.ts:86](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L86) +Defined in: [packages/db/src/indexes/base-index.ts:95](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L95) #### Inherited from @@ -90,13 +90,30 @@ Defined in: [packages/db/src/indexes/base-index.ts:86](https://github.com/TanSta *** +### hasCustomComparator + +```ts +protected hasCustomComparator: boolean = false; +``` + +Defined in: [packages/db/src/indexes/base-index.ts:106](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L106) + +Set by subclasses when constructed with a user-supplied comparator, whose +ordering may not match the WHERE evaluator's relational operators. + +#### Inherited from + +[`BaseIndex`](BaseIndex.md).[`hasCustomComparator`](BaseIndex.md#hascustomcomparator) + +*** + ### id ```ts readonly id: number; ``` -Defined in: [packages/db/src/indexes/base-index.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L84) +Defined in: [packages/db/src/indexes/base-index.ts:93](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L93) #### Inherited from @@ -110,7 +127,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:84](https://github.com/TanSta protected lastUpdated: Date; ``` -Defined in: [packages/db/src/indexes/base-index.ts:91](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L91) +Defined in: [packages/db/src/indexes/base-index.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L100) #### Inherited from @@ -124,7 +141,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:91](https://github.com/TanSta protected lookupCount: number = 0; ``` -Defined in: [packages/db/src/indexes/base-index.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L89) +Defined in: [packages/db/src/indexes/base-index.ts:98](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L98) #### Inherited from @@ -138,7 +155,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:89](https://github.com/TanSta readonly optional name: string; ``` -Defined in: [packages/db/src/indexes/base-index.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L85) +Defined in: [packages/db/src/indexes/base-index.ts:94](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L94) #### Inherited from @@ -166,7 +183,7 @@ Defined in: [packages/db/src/indexes/btree-index.ts:38](https://github.com/TanSt protected totalLookupTime: number = 0; ``` -Defined in: [packages/db/src/indexes/base-index.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L90) +Defined in: [packages/db/src/indexes/base-index.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L99) #### Inherited from @@ -182,7 +199,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:90](https://github.com/TanSta get indexedKeysSet(): Set; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:400](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L400) +Defined in: [packages/db/src/indexes/btree-index.ts:410](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L410) ##### Returns @@ -202,7 +219,7 @@ Defined in: [packages/db/src/indexes/btree-index.ts:400](https://github.com/TanS get keyCount(): number; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:213](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L213) +Defined in: [packages/db/src/indexes/btree-index.ts:214](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L214) Gets the number of indexed keys @@ -224,7 +241,7 @@ Gets the number of indexed keys get orderedEntriesArray(): [any, Set][]; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:404](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L404) +Defined in: [packages/db/src/indexes/btree-index.ts:414](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L414) ##### Returns @@ -244,7 +261,7 @@ Defined in: [packages/db/src/indexes/btree-index.ts:404](https://github.com/TanS get orderedEntriesArrayReversed(): [any, Set][]; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:413](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L413) +Defined in: [packages/db/src/indexes/btree-index.ts:423](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L423) ##### Returns @@ -256,6 +273,32 @@ Defined in: [packages/db/src/indexes/btree-index.ts:413](https://github.com/TanS *** +### supportsRangeOptimization + +#### Get Signature + +```ts +get supportsRangeOptimization(): boolean; +``` + +Defined in: [packages/db/src/indexes/base-index.ts:161](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L161) + +Whether range lookups (gt/gte/lt/lte) on this index can be trusted to +return every matching key. Range traversal relies on the index ordering, so +it is unsafe when the index uses a custom comparator, whose order may not +match the WHERE evaluator's relational operators. Callers must fall back to +a full scan when this is `false`. + +##### Returns + +`boolean` + +#### Inherited from + +[`BaseIndex`](BaseIndex.md).[`supportsRangeOptimization`](BaseIndex.md#supportsrangeoptimization) + +*** + ### valueMapData #### Get Signature @@ -264,7 +307,7 @@ Defined in: [packages/db/src/indexes/btree-index.ts:413](https://github.com/TanS get valueMapData(): Map>; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:420](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L420) +Defined in: [packages/db/src/indexes/btree-index.ts:430](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L430) ##### Returns @@ -282,7 +325,7 @@ Defined in: [packages/db/src/indexes/btree-index.ts:420](https://github.com/TanS add(key, item): void; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L83) +Defined in: [packages/db/src/indexes/btree-index.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L84) Adds a value to the index @@ -312,7 +355,7 @@ Adds a value to the index build(entries): void; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L157) +Defined in: [packages/db/src/indexes/btree-index.ts:158](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L158) Builds the index from a collection of entries @@ -338,7 +381,7 @@ Builds the index from a collection of entries clear(): void; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:168](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L168) +Defined in: [packages/db/src/indexes/btree-index.ts:169](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L169) Clears all data from the index @@ -358,7 +401,7 @@ Clears all data from the index equalityLookup(value): Set; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:222](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L222) +Defined in: [packages/db/src/indexes/btree-index.ts:223](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L223) Performs an equality lookup @@ -384,7 +427,7 @@ Performs an equality lookup protected evaluateIndexExpression(item): any; ``` -Defined in: [packages/db/src/indexes/base-index.ts:194](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L194) +Defined in: [packages/db/src/indexes/base-index.ts:212](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L212) #### Parameters @@ -408,7 +451,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:194](https://github.com/TanSt getStats(): IndexStats; ``` -Defined in: [packages/db/src/indexes/base-index.ts:182](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L182) +Defined in: [packages/db/src/indexes/base-index.ts:200](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L200) #### Returns @@ -426,7 +469,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:182](https://github.com/TanSt inArrayLookup(values): Set; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:385](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L385) +Defined in: [packages/db/src/indexes/btree-index.ts:395](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L395) Performs an IN array lookup @@ -452,7 +495,7 @@ Performs an IN array lookup protected initialize(_options?): void; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L78) +Defined in: [packages/db/src/indexes/btree-index.ts:79](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L79) #### Parameters @@ -476,7 +519,7 @@ Defined in: [packages/db/src/indexes/btree-index.ts:78](https://github.com/TanSt lookup(operation, value): Set; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L178) +Defined in: [packages/db/src/indexes/btree-index.ts:179](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L179) Performs a lookup operation @@ -506,7 +549,7 @@ Performs a lookup operation matchesCompareOptions(compareOptions): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:159](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L159) +Defined in: [packages/db/src/indexes/base-index.ts:177](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L177) Checks if the compare options match the index's compare options. The direction is ignored because the index can be reversed if the direction is different. @@ -533,7 +576,7 @@ The direction is ignored because the index can be reversed if the direction is d matchesDirection(direction): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L178) +Defined in: [packages/db/src/indexes/base-index.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L196) Checks if the index matches the provided direction. @@ -559,7 +602,7 @@ Checks if the index matches the provided direction. matchesField(fieldPath): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:147](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L147) +Defined in: [packages/db/src/indexes/base-index.ts:165](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L165) #### Parameters @@ -583,7 +626,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:147](https://github.com/TanSt rangeQuery(options): Set; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:231](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L231) +Defined in: [packages/db/src/indexes/btree-index.ts:232](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L232) Performs a range query with options This is more efficient for compound queries like "WHERE a > 5 AND a < 10" @@ -610,7 +653,7 @@ This is more efficient for compound queries like "WHERE a > 5 AND a < 10" rangeQueryReversed(options): Set; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:269](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L269) +Defined in: [packages/db/src/indexes/btree-index.ts:279](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L279) Performs a reversed range query @@ -636,7 +679,7 @@ Performs a reversed range query remove(key, item): void; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:114](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L114) +Defined in: [packages/db/src/indexes/btree-index.ts:115](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L115) Removes a value from the index @@ -666,7 +709,7 @@ Removes a value from the index supports(operation): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:143](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L143) +Defined in: [packages/db/src/indexes/base-index.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L157) #### Parameters @@ -693,7 +736,7 @@ take( filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:331](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L331) +Defined in: [packages/db/src/indexes/btree-index.ts:341](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L341) Returns the next n items after the provided item. @@ -733,7 +776,7 @@ The next n items after the provided key. takeFromStart(n, filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:344](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L344) +Defined in: [packages/db/src/indexes/btree-index.ts:354](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L354) Returns the first n items from the beginning. @@ -772,7 +815,7 @@ takeReversed( filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:356](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L356) +Defined in: [packages/db/src/indexes/btree-index.ts:366](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L366) Returns the next n items **before** the provided item (in descending order). @@ -812,7 +855,7 @@ The next n items **before** the provided key. takeReversedFromEnd(n, filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:373](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L373) +Defined in: [packages/db/src/indexes/btree-index.ts:383](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L383) Returns the last n items from the end. @@ -848,7 +891,7 @@ The last n items protected trackLookup(startTime): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:199](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L199) +Defined in: [packages/db/src/indexes/base-index.ts:217](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L217) #### Parameters @@ -875,7 +918,7 @@ update( newItem): void; ``` -Defined in: [packages/db/src/indexes/btree-index.ts:149](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L149) +Defined in: [packages/db/src/indexes/btree-index.ts:150](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/btree-index.ts#L150) Updates a value in the index @@ -909,7 +952,7 @@ Updates a value in the index protected updateTimestamp(): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:205](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L205) +Defined in: [packages/db/src/indexes/base-index.ts:223](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L223) #### Returns diff --git a/docs/reference/classes/BaseIndex.md b/docs/reference/classes/BaseIndex.md index bd512fdbea..90f82ced5b 100644 --- a/docs/reference/classes/BaseIndex.md +++ b/docs/reference/classes/BaseIndex.md @@ -5,7 +5,7 @@ title: BaseIndex # Abstract Class: BaseIndex\ -Defined in: [packages/db/src/indexes/base-index.ts:81](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L81) +Defined in: [packages/db/src/indexes/base-index.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L90) Base abstract class that all index types extend @@ -36,7 +36,7 @@ new BaseIndex( options?): BaseIndex; ``` -Defined in: [packages/db/src/indexes/base-index.ts:94](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L94) +Defined in: [packages/db/src/indexes/base-index.ts:108](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L108) #### Parameters @@ -68,7 +68,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:94](https://github.com/TanSta protected compareOptions: CompareOptions; ``` -Defined in: [packages/db/src/indexes/base-index.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L92) +Defined in: [packages/db/src/indexes/base-index.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L101) *** @@ -78,7 +78,20 @@ Defined in: [packages/db/src/indexes/base-index.ts:92](https://github.com/TanSta readonly expression: BasicExpression; ``` -Defined in: [packages/db/src/indexes/base-index.ts:86](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L86) +Defined in: [packages/db/src/indexes/base-index.ts:95](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L95) + +*** + +### hasCustomComparator + +```ts +protected hasCustomComparator: boolean = false; +``` + +Defined in: [packages/db/src/indexes/base-index.ts:106](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L106) + +Set by subclasses when constructed with a user-supplied comparator, whose +ordering may not match the WHERE evaluator's relational operators. *** @@ -88,7 +101,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:86](https://github.com/TanSta readonly id: number; ``` -Defined in: [packages/db/src/indexes/base-index.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L84) +Defined in: [packages/db/src/indexes/base-index.ts:93](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L93) *** @@ -98,7 +111,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:84](https://github.com/TanSta protected lastUpdated: Date; ``` -Defined in: [packages/db/src/indexes/base-index.ts:91](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L91) +Defined in: [packages/db/src/indexes/base-index.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L100) *** @@ -108,7 +121,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:91](https://github.com/TanSta protected lookupCount: number = 0; ``` -Defined in: [packages/db/src/indexes/base-index.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L89) +Defined in: [packages/db/src/indexes/base-index.ts:98](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L98) *** @@ -118,7 +131,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:89](https://github.com/TanSta readonly optional name: string; ``` -Defined in: [packages/db/src/indexes/base-index.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L85) +Defined in: [packages/db/src/indexes/base-index.ts:94](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L94) *** @@ -128,7 +141,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:85](https://github.com/TanSta abstract readonly supportedOperations: Set<"eq" | "gt" | "gte" | "lt" | "lte" | "in" | "like" | "ilike">; ``` -Defined in: [packages/db/src/indexes/base-index.ts:87](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L87) +Defined in: [packages/db/src/indexes/base-index.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L96) *** @@ -138,7 +151,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:87](https://github.com/TanSta protected totalLookupTime: number = 0; ``` -Defined in: [packages/db/src/indexes/base-index.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L90) +Defined in: [packages/db/src/indexes/base-index.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L99) ## Accessors @@ -150,7 +163,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:90](https://github.com/TanSta get abstract indexedKeysSet(): Set; ``` -Defined in: [packages/db/src/indexes/base-index.ts:139](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L139) +Defined in: [packages/db/src/indexes/base-index.ts:153](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L153) ##### Returns @@ -170,7 +183,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:139](https://github.com/TanSt get abstract keyCount(): number; ``` -Defined in: [packages/db/src/indexes/base-index.ts:132](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L132) +Defined in: [packages/db/src/indexes/base-index.ts:146](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L146) ##### Returns @@ -190,7 +203,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:132](https://github.com/TanSt get abstract orderedEntriesArray(): [any, Set][]; ``` -Defined in: [packages/db/src/indexes/base-index.ts:137](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L137) +Defined in: [packages/db/src/indexes/base-index.ts:151](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L151) ##### Returns @@ -210,7 +223,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:137](https://github.com/TanSt get abstract orderedEntriesArrayReversed(): [any, Set][]; ``` -Defined in: [packages/db/src/indexes/base-index.ts:138](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L138) +Defined in: [packages/db/src/indexes/base-index.ts:152](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L152) ##### Returns @@ -222,6 +235,32 @@ Defined in: [packages/db/src/indexes/base-index.ts:138](https://github.com/TanSt *** +### supportsRangeOptimization + +#### Get Signature + +```ts +get supportsRangeOptimization(): boolean; +``` + +Defined in: [packages/db/src/indexes/base-index.ts:161](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L161) + +Whether range lookups (gt/gte/lt/lte) on this index can be trusted to +return every matching key. Range traversal relies on the index ordering, so +it is unsafe when the index uses a custom comparator, whose order may not +match the WHERE evaluator's relational operators. Callers must fall back to +a full scan when this is `false`. + +##### Returns + +`boolean` + +#### Implementation of + +[`IndexInterface`](../interfaces/IndexInterface.md).[`supportsRangeOptimization`](../interfaces/IndexInterface.md#supportsrangeoptimization) + +*** + ### valueMapData #### Get Signature @@ -230,7 +269,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:138](https://github.com/TanSt get abstract valueMapData(): Map>; ``` -Defined in: [packages/db/src/indexes/base-index.ts:140](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L140) +Defined in: [packages/db/src/indexes/base-index.ts:154](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L154) ##### Returns @@ -248,7 +287,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:140](https://github.com/TanSt abstract add(key, item): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:108](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L108) +Defined in: [packages/db/src/indexes/base-index.ts:122](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L122) #### Parameters @@ -276,7 +315,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:108](https://github.com/TanSt abstract build(entries): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:111](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L111) +Defined in: [packages/db/src/indexes/base-index.ts:125](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L125) #### Parameters @@ -300,7 +339,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:111](https://github.com/TanSt abstract clear(): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:112](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L112) +Defined in: [packages/db/src/indexes/base-index.ts:126](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L126) #### Returns @@ -318,7 +357,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:112](https://github.com/TanSt abstract equalityLookup(value): Set; ``` -Defined in: [packages/db/src/indexes/base-index.ts:133](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L133) +Defined in: [packages/db/src/indexes/base-index.ts:147](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L147) #### Parameters @@ -342,7 +381,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:133](https://github.com/TanSt protected evaluateIndexExpression(item): any; ``` -Defined in: [packages/db/src/indexes/base-index.ts:194](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L194) +Defined in: [packages/db/src/indexes/base-index.ts:212](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L212) #### Parameters @@ -362,7 +401,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:194](https://github.com/TanSt getStats(): IndexStats; ``` -Defined in: [packages/db/src/indexes/base-index.ts:182](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L182) +Defined in: [packages/db/src/indexes/base-index.ts:200](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L200) #### Returns @@ -380,7 +419,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:182](https://github.com/TanSt abstract inArrayLookup(values): Set; ``` -Defined in: [packages/db/src/indexes/base-index.ts:134](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L134) +Defined in: [packages/db/src/indexes/base-index.ts:148](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L148) #### Parameters @@ -404,7 +443,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:134](https://github.com/TanSt abstract protected initialize(options?): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:192](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L192) +Defined in: [packages/db/src/indexes/base-index.ts:210](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L210) #### Parameters @@ -424,7 +463,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:192](https://github.com/TanSt abstract lookup(operation, value): Set; ``` -Defined in: [packages/db/src/indexes/base-index.ts:113](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L113) +Defined in: [packages/db/src/indexes/base-index.ts:127](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L127) #### Parameters @@ -452,7 +491,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:113](https://github.com/TanSt matchesCompareOptions(compareOptions): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:159](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L159) +Defined in: [packages/db/src/indexes/base-index.ts:177](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L177) Checks if the compare options match the index's compare options. The direction is ignored because the index can be reversed if the direction is different. @@ -479,7 +518,7 @@ The direction is ignored because the index can be reversed if the direction is d matchesDirection(direction): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L178) +Defined in: [packages/db/src/indexes/base-index.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L196) Checks if the index matches the provided direction. @@ -505,7 +544,7 @@ Checks if the index matches the provided direction. matchesField(fieldPath): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:147](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L147) +Defined in: [packages/db/src/indexes/base-index.ts:165](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L165) #### Parameters @@ -529,7 +568,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:147](https://github.com/TanSt abstract rangeQuery(options): Set; ``` -Defined in: [packages/db/src/indexes/base-index.ts:135](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L135) +Defined in: [packages/db/src/indexes/base-index.ts:149](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L149) #### Parameters @@ -553,7 +592,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:135](https://github.com/TanSt abstract rangeQueryReversed(options): Set; ``` -Defined in: [packages/db/src/indexes/base-index.ts:136](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L136) +Defined in: [packages/db/src/indexes/base-index.ts:150](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L150) #### Parameters @@ -577,7 +616,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:136](https://github.com/TanSt abstract remove(key, item): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:109](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L109) +Defined in: [packages/db/src/indexes/base-index.ts:123](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L123) #### Parameters @@ -605,7 +644,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:109](https://github.com/TanSt supports(operation): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:143](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L143) +Defined in: [packages/db/src/indexes/base-index.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L157) #### Parameters @@ -632,7 +671,7 @@ abstract take( filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/base-index.ts:114](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L114) +Defined in: [packages/db/src/indexes/base-index.ts:128](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L128) #### Parameters @@ -664,7 +703,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:114](https://github.com/TanSt abstract takeFromStart(n, filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/base-index.ts:119](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L119) +Defined in: [packages/db/src/indexes/base-index.ts:133](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L133) #### Parameters @@ -695,7 +734,7 @@ abstract takeReversed( filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/base-index.ts:123](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L123) +Defined in: [packages/db/src/indexes/base-index.ts:137](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L137) #### Parameters @@ -727,7 +766,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:123](https://github.com/TanSt abstract takeReversedFromEnd(n, filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/base-index.ts:128](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L128) +Defined in: [packages/db/src/indexes/base-index.ts:142](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L142) #### Parameters @@ -755,7 +794,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:128](https://github.com/TanSt protected trackLookup(startTime): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:199](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L199) +Defined in: [packages/db/src/indexes/base-index.ts:217](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L217) #### Parameters @@ -778,7 +817,7 @@ abstract update( newItem): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:110](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L110) +Defined in: [packages/db/src/indexes/base-index.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L124) #### Parameters @@ -810,7 +849,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:110](https://github.com/TanSt protected updateTimestamp(): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:205](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L205) +Defined in: [packages/db/src/indexes/base-index.ts:223](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L223) #### Returns diff --git a/docs/reference/classes/BasicIndex.md b/docs/reference/classes/BasicIndex.md index 35df050713..7e6ab4cec6 100644 --- a/docs/reference/classes/BasicIndex.md +++ b/docs/reference/classes/BasicIndex.md @@ -74,7 +74,7 @@ Defined in: [packages/db/src/indexes/basic-index.ts:60](https://github.com/TanSt protected compareOptions: CompareOptions; ``` -Defined in: [packages/db/src/indexes/base-index.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L92) +Defined in: [packages/db/src/indexes/base-index.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L101) #### Inherited from @@ -88,7 +88,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:92](https://github.com/TanSta readonly expression: BasicExpression; ``` -Defined in: [packages/db/src/indexes/base-index.ts:86](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L86) +Defined in: [packages/db/src/indexes/base-index.ts:95](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L95) #### Inherited from @@ -96,13 +96,30 @@ Defined in: [packages/db/src/indexes/base-index.ts:86](https://github.com/TanSta *** +### hasCustomComparator + +```ts +protected hasCustomComparator: boolean = false; +``` + +Defined in: [packages/db/src/indexes/base-index.ts:106](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L106) + +Set by subclasses when constructed with a user-supplied comparator, whose +ordering may not match the WHERE evaluator's relational operators. + +#### Inherited from + +[`BaseIndex`](BaseIndex.md).[`hasCustomComparator`](BaseIndex.md#hascustomcomparator) + +*** + ### id ```ts readonly id: number; ``` -Defined in: [packages/db/src/indexes/base-index.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L84) +Defined in: [packages/db/src/indexes/base-index.ts:93](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L93) #### Inherited from @@ -116,7 +133,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:84](https://github.com/TanSta protected lastUpdated: Date; ``` -Defined in: [packages/db/src/indexes/base-index.ts:91](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L91) +Defined in: [packages/db/src/indexes/base-index.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L100) #### Inherited from @@ -130,7 +147,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:91](https://github.com/TanSta protected lookupCount: number = 0; ``` -Defined in: [packages/db/src/indexes/base-index.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L89) +Defined in: [packages/db/src/indexes/base-index.ts:98](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L98) #### Inherited from @@ -144,7 +161,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:89](https://github.com/TanSta readonly optional name: string; ``` -Defined in: [packages/db/src/indexes/base-index.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L85) +Defined in: [packages/db/src/indexes/base-index.ts:94](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L94) #### Inherited from @@ -172,7 +189,7 @@ Defined in: [packages/db/src/indexes/basic-index.ts:42](https://github.com/TanSt protected totalLookupTime: number = 0; ``` -Defined in: [packages/db/src/indexes/base-index.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L90) +Defined in: [packages/db/src/indexes/base-index.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L99) #### Inherited from @@ -188,7 +205,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:90](https://github.com/TanSta get indexedKeysSet(): Set; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:484](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L484) +Defined in: [packages/db/src/indexes/basic-index.ts:485](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L485) ##### Returns @@ -208,7 +225,7 @@ Defined in: [packages/db/src/indexes/basic-index.ts:484](https://github.com/TanS get keyCount(): number; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:238](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L238) +Defined in: [packages/db/src/indexes/basic-index.ts:239](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L239) Gets the number of indexed keys @@ -230,7 +247,7 @@ Gets the number of indexed keys get orderedEntriesArray(): [any, Set][]; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:488](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L488) +Defined in: [packages/db/src/indexes/basic-index.ts:489](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L489) ##### Returns @@ -250,7 +267,7 @@ Defined in: [packages/db/src/indexes/basic-index.ts:488](https://github.com/TanS get orderedEntriesArrayReversed(): [any, Set][]; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:495](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L495) +Defined in: [packages/db/src/indexes/basic-index.ts:496](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L496) ##### Returns @@ -262,6 +279,32 @@ Defined in: [packages/db/src/indexes/basic-index.ts:495](https://github.com/TanS *** +### supportsRangeOptimization + +#### Get Signature + +```ts +get supportsRangeOptimization(): boolean; +``` + +Defined in: [packages/db/src/indexes/base-index.ts:161](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L161) + +Whether range lookups (gt/gte/lt/lte) on this index can be trusted to +return every matching key. Range traversal relies on the index ordering, so +it is unsafe when the index uses a custom comparator, whose order may not +match the WHERE evaluator's relational operators. Callers must fall back to +a full scan when this is `false`. + +##### Returns + +`boolean` + +#### Inherited from + +[`BaseIndex`](BaseIndex.md).[`supportsRangeOptimization`](BaseIndex.md#supportsrangeoptimization) + +*** + ### valueMapData #### Get Signature @@ -270,7 +313,7 @@ Defined in: [packages/db/src/indexes/basic-index.ts:495](https://github.com/TanS get valueMapData(): Map>; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:504](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L504) +Defined in: [packages/db/src/indexes/basic-index.ts:505](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L505) ##### Returns @@ -288,7 +331,7 @@ Defined in: [packages/db/src/indexes/basic-index.ts:504](https://github.com/TanS add(key, item): void; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L78) +Defined in: [packages/db/src/indexes/basic-index.ts:79](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L79) Adds a value to the index @@ -318,7 +361,7 @@ Adds a value to the index build(entries): void; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:156](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L156) +Defined in: [packages/db/src/indexes/basic-index.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L157) Builds the index from a collection of entries @@ -344,7 +387,7 @@ Builds the index from a collection of entries clear(): void; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:193](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L193) +Defined in: [packages/db/src/indexes/basic-index.ts:194](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L194) Clears all data from the index @@ -364,7 +407,7 @@ Clears all data from the index equalityLookup(value): Set; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:245](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L245) +Defined in: [packages/db/src/indexes/basic-index.ts:246](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L246) Performs an equality lookup - O(1) @@ -390,7 +433,7 @@ Performs an equality lookup - O(1) protected evaluateIndexExpression(item): any; ``` -Defined in: [packages/db/src/indexes/base-index.ts:194](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L194) +Defined in: [packages/db/src/indexes/base-index.ts:212](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L212) #### Parameters @@ -414,7 +457,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:194](https://github.com/TanSt getStats(): IndexStats; ``` -Defined in: [packages/db/src/indexes/base-index.ts:182](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L182) +Defined in: [packages/db/src/indexes/base-index.ts:200](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L200) #### Returns @@ -432,7 +475,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:182](https://github.com/TanSt inArrayLookup(values): Set; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:469](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L469) +Defined in: [packages/db/src/indexes/basic-index.ts:470](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L470) Performs an IN array lookup - O(k) where k is values.length @@ -458,7 +501,7 @@ Performs an IN array lookup - O(k) where k is values.length protected initialize(_options?): void; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L73) +Defined in: [packages/db/src/indexes/basic-index.ts:74](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L74) #### Parameters @@ -482,7 +525,7 @@ Defined in: [packages/db/src/indexes/basic-index.ts:73](https://github.com/TanSt lookup(operation, value): Set; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:203](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L203) +Defined in: [packages/db/src/indexes/basic-index.ts:204](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L204) Performs a lookup operation @@ -512,7 +555,7 @@ Performs a lookup operation matchesCompareOptions(compareOptions): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:159](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L159) +Defined in: [packages/db/src/indexes/base-index.ts:177](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L177) Checks if the compare options match the index's compare options. The direction is ignored because the index can be reversed if the direction is different. @@ -539,7 +582,7 @@ The direction is ignored because the index can be reversed if the direction is d matchesDirection(direction): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L178) +Defined in: [packages/db/src/indexes/base-index.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L196) Checks if the index matches the provided direction. @@ -565,7 +608,7 @@ Checks if the index matches the provided direction. matchesField(fieldPath): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:147](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L147) +Defined in: [packages/db/src/indexes/base-index.ts:165](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L165) #### Parameters @@ -589,7 +632,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:147](https://github.com/TanSt rangeQuery(options): Set; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:253](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L253) +Defined in: [packages/db/src/indexes/basic-index.ts:254](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L254) Performs a range query using binary search - O(log n + m) @@ -615,7 +658,7 @@ Performs a range query using binary search - O(log n + m) rangeQueryReversed(options): Set; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:314](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L314) +Defined in: [packages/db/src/indexes/basic-index.ts:315](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L315) Performs a reversed range query @@ -641,7 +684,7 @@ Performs a reversed range query remove(key, item): void; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:114](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L114) +Defined in: [packages/db/src/indexes/basic-index.ts:115](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L115) Removes a value from the index @@ -671,7 +714,7 @@ Removes a value from the index supports(operation): boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:143](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L143) +Defined in: [packages/db/src/indexes/base-index.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L157) #### Parameters @@ -698,7 +741,7 @@ take( filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:339](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L339) +Defined in: [packages/db/src/indexes/basic-index.ts:340](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L340) Returns the next n items in sorted order @@ -732,7 +775,7 @@ Returns the next n items in sorted order takeFromStart(n, filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:424](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L424) +Defined in: [packages/db/src/indexes/basic-index.ts:425](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L425) Returns the first n items in sorted order (from the start) @@ -765,7 +808,7 @@ takeReversed( filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:381](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L381) +Defined in: [packages/db/src/indexes/basic-index.ts:382](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L382) Returns the next n items in reverse sorted order @@ -799,7 +842,7 @@ Returns the next n items in reverse sorted order takeReversedFromEnd(n, filterFn?): TKey[]; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:443](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L443) +Defined in: [packages/db/src/indexes/basic-index.ts:444](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L444) Returns the first n items in reverse sorted order (from the end) @@ -829,7 +872,7 @@ Returns the first n items in reverse sorted order (from the end) protected trackLookup(startTime): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:199](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L199) +Defined in: [packages/db/src/indexes/base-index.ts:217](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L217) #### Parameters @@ -856,7 +899,7 @@ update( newItem): void; ``` -Defined in: [packages/db/src/indexes/basic-index.ts:148](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L148) +Defined in: [packages/db/src/indexes/basic-index.ts:149](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/basic-index.ts#L149) Updates a value in the index @@ -890,7 +933,7 @@ Updates a value in the index protected updateTimestamp(): void; ``` -Defined in: [packages/db/src/indexes/base-index.ts:205](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L205) +Defined in: [packages/db/src/indexes/base-index.ts:223](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L223) #### Returns diff --git a/docs/reference/classes/CannotCombineEmptyExpressionListError.md b/docs/reference/classes/CannotCombineEmptyExpressionListError.md index 40b7f9be02..caa115e143 100644 --- a/docs/reference/classes/CannotCombineEmptyExpressionListError.md +++ b/docs/reference/classes/CannotCombineEmptyExpressionListError.md @@ -5,7 +5,7 @@ title: CannotCombineEmptyExpressionListError # Class: CannotCombineEmptyExpressionListError -Defined in: [packages/db/src/errors.ts:708](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L708) +Defined in: [packages/db/src/errors.ts:718](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L718) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:708](https://github.com/TanStack/db/blob/ new CannotCombineEmptyExpressionListError(): CannotCombineEmptyExpressionListError; ``` -Defined in: [packages/db/src/errors.ts:709](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L709) +Defined in: [packages/db/src/errors.ts:719](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L719) #### Returns diff --git a/docs/reference/classes/CollectionInputNotFoundError.md b/docs/reference/classes/CollectionInputNotFoundError.md index d818d15d28..0dff36d276 100644 --- a/docs/reference/classes/CollectionInputNotFoundError.md +++ b/docs/reference/classes/CollectionInputNotFoundError.md @@ -5,7 +5,7 @@ title: CollectionInputNotFoundError # Class: CollectionInputNotFoundError -Defined in: [packages/db/src/errors.ts:489](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L489) +Defined in: [packages/db/src/errors.ts:499](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L499) Error thrown when a collection input stream is not found during query compilation. In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream. @@ -25,7 +25,7 @@ new CollectionInputNotFoundError( availableKeys?): CollectionInputNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:490](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L490) +Defined in: [packages/db/src/errors.ts:500](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L500) #### Parameters diff --git a/docs/reference/classes/DistinctRequiresSelectError.md b/docs/reference/classes/DistinctRequiresSelectError.md index ff286020b5..15b7fcd2fe 100644 --- a/docs/reference/classes/DistinctRequiresSelectError.md +++ b/docs/reference/classes/DistinctRequiresSelectError.md @@ -5,7 +5,7 @@ title: DistinctRequiresSelectError # Class: DistinctRequiresSelectError -Defined in: [packages/db/src/errors.ts:445](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L445) +Defined in: [packages/db/src/errors.ts:455](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L455) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:445](https://github.com/TanStack/db/blob/ new DistinctRequiresSelectError(): DistinctRequiresSelectError; ``` -Defined in: [packages/db/src/errors.ts:446](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L446) +Defined in: [packages/db/src/errors.ts:456](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L456) #### Returns diff --git a/docs/reference/classes/DuplicateAliasInSubqueryError.md b/docs/reference/classes/DuplicateAliasInSubqueryError.md index 728f2c6947..3caada57de 100644 --- a/docs/reference/classes/DuplicateAliasInSubqueryError.md +++ b/docs/reference/classes/DuplicateAliasInSubqueryError.md @@ -5,7 +5,7 @@ title: DuplicateAliasInSubqueryError # Class: DuplicateAliasInSubqueryError -Defined in: [packages/db/src/errors.ts:510](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L510) +Defined in: [packages/db/src/errors.ts:520](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L520) Error thrown when a subquery uses the same alias as its parent query. This causes issues because parent and subquery would share the same input streams, @@ -23,7 +23,7 @@ leading to empty results or incorrect data (aggregation cross-leaking). new DuplicateAliasInSubqueryError(alias, parentAliases): DuplicateAliasInSubqueryError; ``` -Defined in: [packages/db/src/errors.ts:511](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L511) +Defined in: [packages/db/src/errors.ts:521](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L521) #### Parameters diff --git a/docs/reference/classes/EmptyReferencePathError.md b/docs/reference/classes/EmptyReferencePathError.md index b81a7f9f31..7881e98bcf 100644 --- a/docs/reference/classes/EmptyReferencePathError.md +++ b/docs/reference/classes/EmptyReferencePathError.md @@ -5,7 +5,7 @@ title: EmptyReferencePathError # Class: EmptyReferencePathError -Defined in: [packages/db/src/errors.ts:533](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L533) +Defined in: [packages/db/src/errors.ts:543](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L543) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:533](https://github.com/TanStack/db/blob/ new EmptyReferencePathError(): EmptyReferencePathError; ``` -Defined in: [packages/db/src/errors.ts:534](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L534) +Defined in: [packages/db/src/errors.ts:544](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L544) #### Returns diff --git a/docs/reference/classes/FnSelectWithGroupByError.md b/docs/reference/classes/FnSelectWithGroupByError.md index 5fd37e7515..83f4fd0e33 100644 --- a/docs/reference/classes/FnSelectWithGroupByError.md +++ b/docs/reference/classes/FnSelectWithGroupByError.md @@ -5,7 +5,7 @@ title: FnSelectWithGroupByError # Class: FnSelectWithGroupByError -Defined in: [packages/db/src/errors.ts:451](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L451) +Defined in: [packages/db/src/errors.ts:461](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L461) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:451](https://github.com/TanStack/db/blob/ new FnSelectWithGroupByError(): FnSelectWithGroupByError; ``` -Defined in: [packages/db/src/errors.ts:452](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L452) +Defined in: [packages/db/src/errors.ts:462](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L462) #### Returns diff --git a/docs/reference/classes/GroupByError.md b/docs/reference/classes/GroupByError.md index 84e2b2cecf..96ecd611be 100644 --- a/docs/reference/classes/GroupByError.md +++ b/docs/reference/classes/GroupByError.md @@ -5,7 +5,7 @@ title: GroupByError # Class: GroupByError -Defined in: [packages/db/src/errors.ts:608](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L608) +Defined in: [packages/db/src/errors.ts:618](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L618) ## Extends @@ -26,7 +26,7 @@ Defined in: [packages/db/src/errors.ts:608](https://github.com/TanStack/db/blob/ new GroupByError(message): GroupByError; ``` -Defined in: [packages/db/src/errors.ts:609](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L609) +Defined in: [packages/db/src/errors.ts:619](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L619) #### Parameters diff --git a/docs/reference/classes/HavingRequiresGroupByError.md b/docs/reference/classes/HavingRequiresGroupByError.md index ee34aa6483..c4a7af348a 100644 --- a/docs/reference/classes/HavingRequiresGroupByError.md +++ b/docs/reference/classes/HavingRequiresGroupByError.md @@ -5,7 +5,7 @@ title: HavingRequiresGroupByError # Class: HavingRequiresGroupByError -Defined in: [packages/db/src/errors.ts:471](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L471) +Defined in: [packages/db/src/errors.ts:481](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L481) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:471](https://github.com/TanStack/db/blob/ new HavingRequiresGroupByError(): HavingRequiresGroupByError; ``` -Defined in: [packages/db/src/errors.ts:472](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L472) +Defined in: [packages/db/src/errors.ts:482](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L482) #### Returns diff --git a/docs/reference/classes/InvalidJoinCondition.md b/docs/reference/classes/InvalidJoinCondition.md index f4404ddc16..2079d31c5c 100644 --- a/docs/reference/classes/InvalidJoinCondition.md +++ b/docs/reference/classes/InvalidJoinCondition.md @@ -5,7 +5,7 @@ title: InvalidJoinCondition # Class: InvalidJoinCondition -Defined in: [packages/db/src/errors.ts:595](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L595) +Defined in: [packages/db/src/errors.ts:605](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L605) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:595](https://github.com/TanStack/db/blob/ new InvalidJoinCondition(): InvalidJoinCondition; ``` -Defined in: [packages/db/src/errors.ts:596](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L596) +Defined in: [packages/db/src/errors.ts:606](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L606) #### Returns diff --git a/docs/reference/classes/InvalidJoinConditionLeftSourceError.md b/docs/reference/classes/InvalidJoinConditionLeftSourceError.md index 2e48bf18ba..f0abb3daa6 100644 --- a/docs/reference/classes/InvalidJoinConditionLeftSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionLeftSourceError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionLeftSourceError # Class: InvalidJoinConditionLeftSourceError -Defined in: [packages/db/src/errors.ts:579](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L579) +Defined in: [packages/db/src/errors.ts:589](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L589) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:579](https://github.com/TanStack/db/blob/ new InvalidJoinConditionLeftSourceError(sourceAlias): InvalidJoinConditionLeftSourceError; ``` -Defined in: [packages/db/src/errors.ts:580](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L580) +Defined in: [packages/db/src/errors.ts:590](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L590) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionRightSourceError.md b/docs/reference/classes/InvalidJoinConditionRightSourceError.md index 2c41a52807..7d559c3e48 100644 --- a/docs/reference/classes/InvalidJoinConditionRightSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionRightSourceError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionRightSourceError # Class: InvalidJoinConditionRightSourceError -Defined in: [packages/db/src/errors.ts:587](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L587) +Defined in: [packages/db/src/errors.ts:597](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L597) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:587](https://github.com/TanStack/db/blob/ new InvalidJoinConditionRightSourceError(sourceAlias): InvalidJoinConditionRightSourceError; ``` -Defined in: [packages/db/src/errors.ts:588](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L588) +Defined in: [packages/db/src/errors.ts:598](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L598) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionSameSourceError.md b/docs/reference/classes/InvalidJoinConditionSameSourceError.md index 221d32f470..f6fa518a41 100644 --- a/docs/reference/classes/InvalidJoinConditionSameSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionSameSourceError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionSameSourceError # Class: InvalidJoinConditionSameSourceError -Defined in: [packages/db/src/errors.ts:565](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L565) +Defined in: [packages/db/src/errors.ts:575](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L575) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:565](https://github.com/TanStack/db/blob/ new InvalidJoinConditionSameSourceError(sourceAlias): InvalidJoinConditionSameSourceError; ``` -Defined in: [packages/db/src/errors.ts:566](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L566) +Defined in: [packages/db/src/errors.ts:576](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L576) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md b/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md index c588f019da..5e7f0db7da 100644 --- a/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md +++ b/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md @@ -5,7 +5,7 @@ title: InvalidJoinConditionSourceMismatchError # Class: InvalidJoinConditionSourceMismatchError -Defined in: [packages/db/src/errors.ts:573](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L573) +Defined in: [packages/db/src/errors.ts:583](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L583) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:573](https://github.com/TanStack/db/blob/ new InvalidJoinConditionSourceMismatchError(): InvalidJoinConditionSourceMismatchError; ``` -Defined in: [packages/db/src/errors.ts:574](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L574) +Defined in: [packages/db/src/errors.ts:584](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L584) #### Returns diff --git a/docs/reference/classes/InvalidStorageDataFormatError.md b/docs/reference/classes/InvalidStorageDataFormatError.md index 77a251d8d9..12a18460c6 100644 --- a/docs/reference/classes/InvalidStorageDataFormatError.md +++ b/docs/reference/classes/InvalidStorageDataFormatError.md @@ -5,7 +5,7 @@ title: InvalidStorageDataFormatError # Class: InvalidStorageDataFormatError -Defined in: [packages/db/src/errors.ts:673](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L673) +Defined in: [packages/db/src/errors.ts:683](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L683) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:673](https://github.com/TanStack/db/blob/ new InvalidStorageDataFormatError(storageKey, key): InvalidStorageDataFormatError; ``` -Defined in: [packages/db/src/errors.ts:674](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L674) +Defined in: [packages/db/src/errors.ts:684](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L684) #### Parameters diff --git a/docs/reference/classes/InvalidStorageObjectFormatError.md b/docs/reference/classes/InvalidStorageObjectFormatError.md index 293360e806..92161868e9 100644 --- a/docs/reference/classes/InvalidStorageObjectFormatError.md +++ b/docs/reference/classes/InvalidStorageObjectFormatError.md @@ -5,7 +5,7 @@ title: InvalidStorageObjectFormatError # Class: InvalidStorageObjectFormatError -Defined in: [packages/db/src/errors.ts:681](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L681) +Defined in: [packages/db/src/errors.ts:691](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L691) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:681](https://github.com/TanStack/db/blob/ new InvalidStorageObjectFormatError(storageKey): InvalidStorageObjectFormatError; ``` -Defined in: [packages/db/src/errors.ts:682](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L682) +Defined in: [packages/db/src/errors.ts:692](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L692) #### Parameters diff --git a/docs/reference/classes/JoinCollectionNotFoundError.md b/docs/reference/classes/JoinCollectionNotFoundError.md index 11b55471fd..fa6a1349b1 100644 --- a/docs/reference/classes/JoinCollectionNotFoundError.md +++ b/docs/reference/classes/JoinCollectionNotFoundError.md @@ -5,7 +5,7 @@ title: JoinCollectionNotFoundError # Class: JoinCollectionNotFoundError -Defined in: [packages/db/src/errors.ts:545](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L545) +Defined in: [packages/db/src/errors.ts:555](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L555) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:545](https://github.com/TanStack/db/blob/ new JoinCollectionNotFoundError(collectionId): JoinCollectionNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:546](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L546) +Defined in: [packages/db/src/errors.ts:556](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L556) #### Parameters diff --git a/docs/reference/classes/JoinError.md b/docs/reference/classes/JoinError.md index 92ecf3288b..126de410b5 100644 --- a/docs/reference/classes/JoinError.md +++ b/docs/reference/classes/JoinError.md @@ -5,7 +5,7 @@ title: JoinError # Class: JoinError -Defined in: [packages/db/src/errors.ts:552](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L552) +Defined in: [packages/db/src/errors.ts:562](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L562) ## Extends @@ -29,7 +29,7 @@ Defined in: [packages/db/src/errors.ts:552](https://github.com/TanStack/db/blob/ new JoinError(message): JoinError; ``` -Defined in: [packages/db/src/errors.ts:553](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L553) +Defined in: [packages/db/src/errors.ts:563](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L563) #### Parameters diff --git a/docs/reference/classes/LimitOffsetRequireOrderByError.md b/docs/reference/classes/LimitOffsetRequireOrderByError.md index 8a7a900522..2d19124468 100644 --- a/docs/reference/classes/LimitOffsetRequireOrderByError.md +++ b/docs/reference/classes/LimitOffsetRequireOrderByError.md @@ -5,7 +5,7 @@ title: LimitOffsetRequireOrderByError # Class: LimitOffsetRequireOrderByError -Defined in: [packages/db/src/errors.ts:477](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L477) +Defined in: [packages/db/src/errors.ts:487](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L487) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:477](https://github.com/TanStack/db/blob/ new LimitOffsetRequireOrderByError(): LimitOffsetRequireOrderByError; ``` -Defined in: [packages/db/src/errors.ts:478](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L478) +Defined in: [packages/db/src/errors.ts:488](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L488) #### Returns diff --git a/docs/reference/classes/LocalStorageCollectionError.md b/docs/reference/classes/LocalStorageCollectionError.md index 55ebac4c72..488fc1b2b4 100644 --- a/docs/reference/classes/LocalStorageCollectionError.md +++ b/docs/reference/classes/LocalStorageCollectionError.md @@ -5,7 +5,7 @@ title: LocalStorageCollectionError # Class: LocalStorageCollectionError -Defined in: [packages/db/src/errors.ts:660](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L660) +Defined in: [packages/db/src/errors.ts:670](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L670) ## Extends @@ -25,7 +25,7 @@ Defined in: [packages/db/src/errors.ts:660](https://github.com/TanStack/db/blob/ new LocalStorageCollectionError(message): LocalStorageCollectionError; ``` -Defined in: [packages/db/src/errors.ts:661](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L661) +Defined in: [packages/db/src/errors.ts:671](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L671) #### Parameters diff --git a/docs/reference/classes/MissingAliasInputsError.md b/docs/reference/classes/MissingAliasInputsError.md index 9a29300c13..4b53a6660d 100644 --- a/docs/reference/classes/MissingAliasInputsError.md +++ b/docs/reference/classes/MissingAliasInputsError.md @@ -5,7 +5,7 @@ title: MissingAliasInputsError # Class: MissingAliasInputsError -Defined in: [packages/db/src/errors.ts:757](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L757) +Defined in: [packages/db/src/errors.ts:767](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L767) Internal error when the compiler returns aliases that don't have corresponding input streams. This should never happen since all aliases come from user declarations. @@ -22,7 +22,7 @@ This should never happen since all aliases come from user declarations. new MissingAliasInputsError(missingAliases): MissingAliasInputsError; ``` -Defined in: [packages/db/src/errors.ts:758](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L758) +Defined in: [packages/db/src/errors.ts:768](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L768) #### Parameters diff --git a/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md b/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md index ea6472fc5c..1b5ceb8f79 100644 --- a/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md +++ b/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md @@ -5,7 +5,7 @@ title: NonAggregateExpressionNotInGroupByError # Class: NonAggregateExpressionNotInGroupByError -Defined in: [packages/db/src/errors.ts:615](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L615) +Defined in: [packages/db/src/errors.ts:625](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L625) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:615](https://github.com/TanStack/db/blob/ new NonAggregateExpressionNotInGroupByError(alias): NonAggregateExpressionNotInGroupByError; ``` -Defined in: [packages/db/src/errors.ts:616](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L616) +Defined in: [packages/db/src/errors.ts:626](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L626) #### Parameters diff --git a/docs/reference/classes/QueryCompilationError.md b/docs/reference/classes/QueryCompilationError.md index cd7d2180a2..cab9dcd58c 100644 --- a/docs/reference/classes/QueryCompilationError.md +++ b/docs/reference/classes/QueryCompilationError.md @@ -13,6 +13,7 @@ Defined in: [packages/db/src/errors.ts:438](https://github.com/TanStack/db/blob/ ## Extended by +- [`UnsafeAliasPathError`](UnsafeAliasPathError.md) - [`DistinctRequiresSelectError`](DistinctRequiresSelectError.md) - [`FnSelectWithGroupByError`](FnSelectWithGroupByError.md) - [`UnsupportedRootScalarSelectError`](UnsupportedRootScalarSelectError.md) diff --git a/docs/reference/classes/QueryOptimizerError.md b/docs/reference/classes/QueryOptimizerError.md index c0c6289e61..4c47eda4dc 100644 --- a/docs/reference/classes/QueryOptimizerError.md +++ b/docs/reference/classes/QueryOptimizerError.md @@ -5,7 +5,7 @@ title: QueryOptimizerError # Class: QueryOptimizerError -Defined in: [packages/db/src/errors.ts:701](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L701) +Defined in: [packages/db/src/errors.ts:711](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L711) ## Extends @@ -24,7 +24,7 @@ Defined in: [packages/db/src/errors.ts:701](https://github.com/TanStack/db/blob/ new QueryOptimizerError(message): QueryOptimizerError; ``` -Defined in: [packages/db/src/errors.ts:702](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L702) +Defined in: [packages/db/src/errors.ts:712](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L712) #### Parameters diff --git a/docs/reference/classes/ReverseIndex.md b/docs/reference/classes/ReverseIndex.md index cf4860b028..162c421317 100644 --- a/docs/reference/classes/ReverseIndex.md +++ b/docs/reference/classes/ReverseIndex.md @@ -47,7 +47,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:11](https://github.com/Tan get indexedKeysSet(): Set; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L124) +Defined in: [packages/db/src/indexes/reverse-index.ts:128](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L128) ##### Returns @@ -67,7 +67,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:124](https://github.com/Ta get keyCount(): number; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:112](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L112) +Defined in: [packages/db/src/indexes/reverse-index.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L116) ##### Returns @@ -119,6 +119,32 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:66](https://github.com/Tan *** +### supportsRangeOptimization + +#### Get Signature + +```ts +get supportsRangeOptimization(): boolean; +``` + +Defined in: [packages/db/src/indexes/reverse-index.ts:76](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L76) + +Whether range lookups (gt/gte/lt/lte) on this index can be trusted to +return every matching key. Range traversal relies on the index ordering, so +it is unsafe when the index uses a custom comparator, whose order may not +match the WHERE evaluator's relational operators. Callers must fall back to +a full scan when this is `false`. + +##### Returns + +`boolean` + +#### Implementation of + +[`IndexInterface`](../interfaces/IndexInterface.md).[`supportsRangeOptimization`](../interfaces/IndexInterface.md#supportsrangeoptimization) + +*** + ### valueMapData #### Get Signature @@ -127,7 +153,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:66](https://github.com/Tan get valueMapData(): Map>; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:128](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L128) +Defined in: [packages/db/src/indexes/reverse-index.ts:132](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L132) ##### Returns @@ -145,7 +171,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:128](https://github.com/Ta add(key, item): void; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L92) +Defined in: [packages/db/src/indexes/reverse-index.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L96) #### Parameters @@ -173,7 +199,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:92](https://github.com/Tan build(entries): void; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:104](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L104) +Defined in: [packages/db/src/indexes/reverse-index.ts:108](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L108) #### Parameters @@ -197,7 +223,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:104](https://github.com/Ta clear(): void; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:108](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L108) +Defined in: [packages/db/src/indexes/reverse-index.ts:112](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L112) #### Returns @@ -215,7 +241,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:108](https://github.com/Ta equalityLookup(value): Set; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L116) +Defined in: [packages/db/src/indexes/reverse-index.ts:120](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L120) #### Parameters @@ -239,7 +265,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:116](https://github.com/Ta getStats(): IndexStats; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L88) +Defined in: [packages/db/src/indexes/reverse-index.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L92) #### Returns @@ -257,7 +283,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:88](https://github.com/Tan inArrayLookup(values): Set; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:120](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L120) +Defined in: [packages/db/src/indexes/reverse-index.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L124) #### Parameters @@ -309,7 +335,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:17](https://github.com/Tan matchesCompareOptions(compareOptions): boolean; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:80](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L80) +Defined in: [packages/db/src/indexes/reverse-index.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L84) #### Parameters @@ -333,7 +359,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:80](https://github.com/Tan matchesDirection(direction): boolean; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L84) +Defined in: [packages/db/src/indexes/reverse-index.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L88) #### Parameters @@ -357,7 +383,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:84](https://github.com/Tan matchesField(fieldPath): boolean; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:76](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L76) +Defined in: [packages/db/src/indexes/reverse-index.ts:80](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L80) #### Parameters @@ -429,7 +455,7 @@ Defined in: [packages/db/src/indexes/reverse-index.ts:35](https://github.com/Tan remove(key, item): void; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L96) +Defined in: [packages/db/src/indexes/reverse-index.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L100) #### Parameters @@ -610,7 +636,7 @@ update( newItem): void; ``` -Defined in: [packages/db/src/indexes/reverse-index.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L100) +Defined in: [packages/db/src/indexes/reverse-index.ts:104](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/reverse-index.ts#L104) #### Parameters diff --git a/docs/reference/classes/SerializationError.md b/docs/reference/classes/SerializationError.md index 5097030ef0..2c209926c5 100644 --- a/docs/reference/classes/SerializationError.md +++ b/docs/reference/classes/SerializationError.md @@ -5,7 +5,7 @@ title: SerializationError # Class: SerializationError -Defined in: [packages/db/src/errors.ts:651](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L651) +Defined in: [packages/db/src/errors.ts:661](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L661) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:651](https://github.com/TanStack/db/blob/ new SerializationError(operation, originalError): SerializationError; ``` -Defined in: [packages/db/src/errors.ts:652](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L652) +Defined in: [packages/db/src/errors.ts:662](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L662) #### Parameters diff --git a/docs/reference/classes/SetWindowRequiresOrderByError.md b/docs/reference/classes/SetWindowRequiresOrderByError.md index ab3ba5ffb3..3f82d3e76c 100644 --- a/docs/reference/classes/SetWindowRequiresOrderByError.md +++ b/docs/reference/classes/SetWindowRequiresOrderByError.md @@ -5,7 +5,7 @@ title: SetWindowRequiresOrderByError # Class: SetWindowRequiresOrderByError -Defined in: [packages/db/src/errors.ts:769](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L769) +Defined in: [packages/db/src/errors.ts:779](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L779) Error thrown when setWindow is called on a collection without an ORDER BY clause. @@ -21,7 +21,7 @@ Error thrown when setWindow is called on a collection without an ORDER BY clause new SetWindowRequiresOrderByError(): SetWindowRequiresOrderByError; ``` -Defined in: [packages/db/src/errors.ts:770](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L770) +Defined in: [packages/db/src/errors.ts:780](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L780) #### Returns diff --git a/docs/reference/classes/StorageError.md b/docs/reference/classes/StorageError.md index fa9fafd553..52e3869adf 100644 --- a/docs/reference/classes/StorageError.md +++ b/docs/reference/classes/StorageError.md @@ -5,7 +5,7 @@ title: StorageError # Class: StorageError -Defined in: [packages/db/src/errors.ts:644](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L644) +Defined in: [packages/db/src/errors.ts:654](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L654) ## Extends @@ -24,7 +24,7 @@ Defined in: [packages/db/src/errors.ts:644](https://github.com/TanStack/db/blob/ new StorageError(message): StorageError; ``` -Defined in: [packages/db/src/errors.ts:645](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L645) +Defined in: [packages/db/src/errors.ts:655](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L655) #### Parameters diff --git a/docs/reference/classes/StorageKeyRequiredError.md b/docs/reference/classes/StorageKeyRequiredError.md index 4dc6d658d9..5cf4e72cdd 100644 --- a/docs/reference/classes/StorageKeyRequiredError.md +++ b/docs/reference/classes/StorageKeyRequiredError.md @@ -5,7 +5,7 @@ title: StorageKeyRequiredError # Class: StorageKeyRequiredError -Defined in: [packages/db/src/errors.ts:667](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L667) +Defined in: [packages/db/src/errors.ts:677](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L677) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:667](https://github.com/TanStack/db/blob/ new StorageKeyRequiredError(): StorageKeyRequiredError; ``` -Defined in: [packages/db/src/errors.ts:668](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L668) +Defined in: [packages/db/src/errors.ts:678](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L678) #### Returns diff --git a/docs/reference/classes/SubscriptionNotFoundError.md b/docs/reference/classes/SubscriptionNotFoundError.md index 77d355a87f..de9841e958 100644 --- a/docs/reference/classes/SubscriptionNotFoundError.md +++ b/docs/reference/classes/SubscriptionNotFoundError.md @@ -5,7 +5,7 @@ title: SubscriptionNotFoundError # Class: SubscriptionNotFoundError -Defined in: [packages/db/src/errors.ts:729](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L729) +Defined in: [packages/db/src/errors.ts:739](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L739) Error when a subscription cannot be found during lazy join processing. For subqueries, aliases may be remapped (e.g., 'activeUser' → 'user'). @@ -26,7 +26,7 @@ new SubscriptionNotFoundError( availableAliases): SubscriptionNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:730](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L730) +Defined in: [packages/db/src/errors.ts:740](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L740) #### Parameters diff --git a/docs/reference/classes/SyncCleanupError.md b/docs/reference/classes/SyncCleanupError.md index 4ca87f307c..6b9031a33f 100644 --- a/docs/reference/classes/SyncCleanupError.md +++ b/docs/reference/classes/SyncCleanupError.md @@ -5,7 +5,7 @@ title: SyncCleanupError # Class: SyncCleanupError -Defined in: [packages/db/src/errors.ts:690](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L690) +Defined in: [packages/db/src/errors.ts:700](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L700) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:690](https://github.com/TanStack/db/blob/ new SyncCleanupError(collectionId, error): SyncCleanupError; ``` -Defined in: [packages/db/src/errors.ts:691](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L691) +Defined in: [packages/db/src/errors.ts:701](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L701) #### Parameters diff --git a/docs/reference/classes/UnknownExpressionTypeError.md b/docs/reference/classes/UnknownExpressionTypeError.md index bf1fca71ce..d223de2501 100644 --- a/docs/reference/classes/UnknownExpressionTypeError.md +++ b/docs/reference/classes/UnknownExpressionTypeError.md @@ -5,7 +5,7 @@ title: UnknownExpressionTypeError # Class: UnknownExpressionTypeError -Defined in: [packages/db/src/errors.ts:527](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L527) +Defined in: [packages/db/src/errors.ts:537](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L537) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:527](https://github.com/TanStack/db/blob/ new UnknownExpressionTypeError(type): UnknownExpressionTypeError; ``` -Defined in: [packages/db/src/errors.ts:528](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L528) +Defined in: [packages/db/src/errors.ts:538](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L538) #### Parameters diff --git a/docs/reference/classes/UnknownFunctionError.md b/docs/reference/classes/UnknownFunctionError.md index c513cd1ca4..3cb73f5f4b 100644 --- a/docs/reference/classes/UnknownFunctionError.md +++ b/docs/reference/classes/UnknownFunctionError.md @@ -5,7 +5,7 @@ title: UnknownFunctionError # Class: UnknownFunctionError -Defined in: [packages/db/src/errors.ts:539](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L539) +Defined in: [packages/db/src/errors.ts:549](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L549) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:539](https://github.com/TanStack/db/blob/ new UnknownFunctionError(functionName): UnknownFunctionError; ``` -Defined in: [packages/db/src/errors.ts:540](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L540) +Defined in: [packages/db/src/errors.ts:550](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L550) #### Parameters diff --git a/docs/reference/classes/UnknownHavingExpressionTypeError.md b/docs/reference/classes/UnknownHavingExpressionTypeError.md index df9f082b73..d2c03792e0 100644 --- a/docs/reference/classes/UnknownHavingExpressionTypeError.md +++ b/docs/reference/classes/UnknownHavingExpressionTypeError.md @@ -5,7 +5,7 @@ title: UnknownHavingExpressionTypeError # Class: UnknownHavingExpressionTypeError -Defined in: [packages/db/src/errors.ts:637](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L637) +Defined in: [packages/db/src/errors.ts:647](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L647) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:637](https://github.com/TanStack/db/blob/ new UnknownHavingExpressionTypeError(type): UnknownHavingExpressionTypeError; ``` -Defined in: [packages/db/src/errors.ts:638](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L638) +Defined in: [packages/db/src/errors.ts:648](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L648) #### Parameters diff --git a/docs/reference/classes/UnsafeAliasPathError.md b/docs/reference/classes/UnsafeAliasPathError.md new file mode 100644 index 0000000000..3140c9f7e1 --- /dev/null +++ b/docs/reference/classes/UnsafeAliasPathError.md @@ -0,0 +1,220 @@ +--- +id: UnsafeAliasPathError +title: UnsafeAliasPathError +--- + +# Class: UnsafeAliasPathError + +Defined in: [packages/db/src/errors.ts:445](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L445) + +## Extends + +- [`QueryCompilationError`](QueryCompilationError.md) + +## Constructors + +### Constructor + +```ts +new UnsafeAliasPathError(segment): UnsafeAliasPathError; +``` + +Defined in: [packages/db/src/errors.ts:446](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L446) + +#### Parameters + +##### segment + +`string` + +#### Returns + +`UnsafeAliasPathError` + +#### Overrides + +[`QueryCompilationError`](QueryCompilationError.md).[`constructor`](QueryCompilationError.md#constructor) + +## Properties + +### cause? + +```ts +optional cause: unknown; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`cause`](QueryCompilationError.md#cause) + +*** + +### message + +```ts +message: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`message`](QueryCompilationError.md#message) + +*** + +### name + +```ts +name: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`name`](QueryCompilationError.md#name) + +*** + +### stack? + +```ts +optional stack: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`stack`](QueryCompilationError.md#stack) + +*** + +### stackTraceLimit + +```ts +static stackTraceLimit: number; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:67 + +The `Error.stackTraceLimit` property specifies the number of stack frames +collected by a stack trace (whether generated by `new Error().stack` or +`Error.captureStackTrace(obj)`). + +The default value is `10` but may be set to any valid JavaScript number. Changes +will affect any stack trace captured _after_ the value has been changed. + +If set to a non-number value, or set to a negative number, stack traces will +not capture any frames. + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`stackTraceLimit`](QueryCompilationError.md#stacktracelimit) + +## Methods + +### captureStackTrace() + +```ts +static captureStackTrace(targetObject, constructorOpt?): void; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:51 + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +#### Parameters + +##### targetObject + +`object` + +##### constructorOpt? + +`Function` + +#### Returns + +`void` + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`captureStackTrace`](QueryCompilationError.md#capturestacktrace) + +*** + +### prepareStackTrace() + +```ts +static prepareStackTrace(err, stackTraces): any; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:55 + +#### Parameters + +##### err + +`Error` + +##### stackTraces + +`CallSite`[] + +#### Returns + +`any` + +#### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`prepareStackTrace`](QueryCompilationError.md#preparestacktrace) diff --git a/docs/reference/classes/UnsupportedAggregateFunctionError.md b/docs/reference/classes/UnsupportedAggregateFunctionError.md index 1e55823861..6f34017e09 100644 --- a/docs/reference/classes/UnsupportedAggregateFunctionError.md +++ b/docs/reference/classes/UnsupportedAggregateFunctionError.md @@ -5,7 +5,7 @@ title: UnsupportedAggregateFunctionError # Class: UnsupportedAggregateFunctionError -Defined in: [packages/db/src/errors.ts:623](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L623) +Defined in: [packages/db/src/errors.ts:633](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L633) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:623](https://github.com/TanStack/db/blob/ new UnsupportedAggregateFunctionError(functionName): UnsupportedAggregateFunctionError; ``` -Defined in: [packages/db/src/errors.ts:624](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L624) +Defined in: [packages/db/src/errors.ts:634](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L634) #### Parameters diff --git a/docs/reference/classes/UnsupportedFromTypeError.md b/docs/reference/classes/UnsupportedFromTypeError.md index 1990e860c5..b0c6499504 100644 --- a/docs/reference/classes/UnsupportedFromTypeError.md +++ b/docs/reference/classes/UnsupportedFromTypeError.md @@ -5,7 +5,7 @@ title: UnsupportedFromTypeError # Class: UnsupportedFromTypeError -Defined in: [packages/db/src/errors.ts:521](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L521) +Defined in: [packages/db/src/errors.ts:531](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L531) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:521](https://github.com/TanStack/db/blob/ new UnsupportedFromTypeError(type): UnsupportedFromTypeError; ``` -Defined in: [packages/db/src/errors.ts:522](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L522) +Defined in: [packages/db/src/errors.ts:532](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L532) #### Parameters diff --git a/docs/reference/classes/UnsupportedJoinSourceTypeError.md b/docs/reference/classes/UnsupportedJoinSourceTypeError.md index 2bb22f90ef..085343980e 100644 --- a/docs/reference/classes/UnsupportedJoinSourceTypeError.md +++ b/docs/reference/classes/UnsupportedJoinSourceTypeError.md @@ -5,7 +5,7 @@ title: UnsupportedJoinSourceTypeError # Class: UnsupportedJoinSourceTypeError -Defined in: [packages/db/src/errors.ts:601](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L601) +Defined in: [packages/db/src/errors.ts:611](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L611) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:601](https://github.com/TanStack/db/blob/ new UnsupportedJoinSourceTypeError(type): UnsupportedJoinSourceTypeError; ``` -Defined in: [packages/db/src/errors.ts:602](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L602) +Defined in: [packages/db/src/errors.ts:612](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L612) #### Parameters diff --git a/docs/reference/classes/UnsupportedJoinTypeError.md b/docs/reference/classes/UnsupportedJoinTypeError.md index 08587d8055..c15081ab54 100644 --- a/docs/reference/classes/UnsupportedJoinTypeError.md +++ b/docs/reference/classes/UnsupportedJoinTypeError.md @@ -5,7 +5,7 @@ title: UnsupportedJoinTypeError # Class: UnsupportedJoinTypeError -Defined in: [packages/db/src/errors.ts:559](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L559) +Defined in: [packages/db/src/errors.ts:569](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L569) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:559](https://github.com/TanStack/db/blob/ new UnsupportedJoinTypeError(joinType): UnsupportedJoinTypeError; ``` -Defined in: [packages/db/src/errors.ts:560](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L560) +Defined in: [packages/db/src/errors.ts:570](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L570) #### Parameters diff --git a/docs/reference/classes/UnsupportedRootScalarSelectError.md b/docs/reference/classes/UnsupportedRootScalarSelectError.md index b2eabacd4e..9b7a87b665 100644 --- a/docs/reference/classes/UnsupportedRootScalarSelectError.md +++ b/docs/reference/classes/UnsupportedRootScalarSelectError.md @@ -5,7 +5,7 @@ title: UnsupportedRootScalarSelectError # Class: UnsupportedRootScalarSelectError -Defined in: [packages/db/src/errors.ts:462](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L462) +Defined in: [packages/db/src/errors.ts:472](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L472) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/db/src/errors.ts:462](https://github.com/TanStack/db/blob/ new UnsupportedRootScalarSelectError(): UnsupportedRootScalarSelectError; ``` -Defined in: [packages/db/src/errors.ts:463](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L463) +Defined in: [packages/db/src/errors.ts:473](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L473) #### Returns diff --git a/docs/reference/classes/WhereClauseConversionError.md b/docs/reference/classes/WhereClauseConversionError.md index a2f0bc2b53..f6d98ac001 100644 --- a/docs/reference/classes/WhereClauseConversionError.md +++ b/docs/reference/classes/WhereClauseConversionError.md @@ -5,7 +5,7 @@ title: WhereClauseConversionError # Class: WhereClauseConversionError -Defined in: [packages/db/src/errors.ts:717](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L717) +Defined in: [packages/db/src/errors.ts:727](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L727) Internal error when the query optimizer fails to convert a WHERE clause to a collection filter. @@ -21,7 +21,7 @@ Internal error when the query optimizer fails to convert a WHERE clause to a col new WhereClauseConversionError(collectionId, alias): WhereClauseConversionError; ``` -Defined in: [packages/db/src/errors.ts:718](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L718) +Defined in: [packages/db/src/errors.ts:728](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L728) #### Parameters diff --git a/docs/reference/functions/compileExpression.md b/docs/reference/functions/compileExpression.md index 1908f7522a..90f18bc152 100644 --- a/docs/reference/functions/compileExpression.md +++ b/docs/reference/functions/compileExpression.md @@ -9,7 +9,7 @@ title: compileExpression function compileExpression(expr, isSingleRow): CompiledSingleRowExpression | CompiledExpression; ``` -Defined in: [packages/db/src/query/compiler/evaluators.ts:72](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/evaluators.ts#L72) +Defined in: [packages/db/src/query/compiler/evaluators.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/evaluators.ts#L89) Compiles an expression into an optimized evaluator function. This eliminates branching during evaluation by pre-compiling the expression structure. diff --git a/docs/reference/functions/compileSingleRowExpression.md b/docs/reference/functions/compileSingleRowExpression.md index 19ab88aa27..7fe2fc727b 100644 --- a/docs/reference/functions/compileSingleRowExpression.md +++ b/docs/reference/functions/compileSingleRowExpression.md @@ -9,7 +9,7 @@ title: compileSingleRowExpression function compileSingleRowExpression(expr): CompiledSingleRowExpression; ``` -Defined in: [packages/db/src/query/compiler/evaluators.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/evaluators.ts#L83) +Defined in: [packages/db/src/query/compiler/evaluators.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/evaluators.ts#L100) Compiles a single-row expression into an optimized evaluator function. diff --git a/docs/reference/functions/findIndexForField.md b/docs/reference/functions/findIndexForField.md index aaab37d8ef..9ff4274941 100644 --- a/docs/reference/functions/findIndexForField.md +++ b/docs/reference/functions/findIndexForField.md @@ -14,7 +14,7 @@ function findIndexForField( | undefined; ``` -Defined in: [packages/db/src/utils/index-optimization.ts:37](https://github.com/TanStack/db/blob/main/packages/db/src/utils/index-optimization.ts#L37) +Defined in: [packages/db/src/utils/index-optimization.ts:45](https://github.com/TanStack/db/blob/main/packages/db/src/utils/index-optimization.ts#L45) Finds an index that matches a given field path diff --git a/docs/reference/functions/optimizeExpressionWithIndexes.md b/docs/reference/functions/optimizeExpressionWithIndexes.md index 31d06210a8..4f0db98160 100644 --- a/docs/reference/functions/optimizeExpressionWithIndexes.md +++ b/docs/reference/functions/optimizeExpressionWithIndexes.md @@ -9,7 +9,7 @@ title: optimizeExpressionWithIndexes function optimizeExpressionWithIndexes(expression, collection): OptimizationResult; ``` -Defined in: [packages/db/src/utils/index-optimization.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/utils/index-optimization.ts#L100) +Defined in: [packages/db/src/utils/index-optimization.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/utils/index-optimization.ts#L196) Optimizes a query expression using available indexes to find matching keys diff --git a/docs/reference/functions/toBooleanPredicate.md b/docs/reference/functions/toBooleanPredicate.md index e1ed96757b..88121e0c23 100644 --- a/docs/reference/functions/toBooleanPredicate.md +++ b/docs/reference/functions/toBooleanPredicate.md @@ -9,7 +9,7 @@ title: toBooleanPredicate function toBooleanPredicate(result): boolean; ``` -Defined in: [packages/db/src/query/compiler/evaluators.ts:54](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/evaluators.ts#L54) +Defined in: [packages/db/src/query/compiler/evaluators.ts:71](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/evaluators.ts#L71) Converts a 3-valued logic result to a boolean for use in WHERE/HAVING filters. In SQL, UNKNOWN (null) values in WHERE clauses exclude rows, matching false behavior. diff --git a/docs/reference/index.md b/docs/reference/index.md index f7d66fcba5..eddfb1cb06 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -99,6 +99,7 @@ title: "@tanstack/db" - [UnknownExpressionTypeError](classes/UnknownExpressionTypeError.md) - [UnknownFunctionError](classes/UnknownFunctionError.md) - [UnknownHavingExpressionTypeError](classes/UnknownHavingExpressionTypeError.md) +- [UnsafeAliasPathError](classes/UnsafeAliasPathError.md) - [UnsupportedAggregateFunctionError](classes/UnsupportedAggregateFunctionError.md) - [UnsupportedFromTypeError](classes/UnsupportedFromTypeError.md) - [UnsupportedJoinSourceTypeError](classes/UnsupportedJoinSourceTypeError.md) diff --git a/docs/reference/interfaces/IndexInterface.md b/docs/reference/interfaces/IndexInterface.md index 43400d877c..a27709eae3 100644 --- a/docs/reference/interfaces/IndexInterface.md +++ b/docs/reference/interfaces/IndexInterface.md @@ -99,7 +99,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:40](https://github.com/TanSta getStats: () => IndexStats; ``` -Defined in: [packages/db/src/indexes/base-index.ts:75](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L75) +Defined in: [packages/db/src/indexes/base-index.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L84) #### Returns @@ -157,7 +157,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:38](https://github.com/TanSta matchesCompareOptions: (compareOptions) => boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:72](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L72) +Defined in: [packages/db/src/indexes/base-index.ts:81](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L81) #### Parameters @@ -177,7 +177,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:72](https://github.com/TanSta matchesDirection: (direction) => boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L73) +Defined in: [packages/db/src/indexes/base-index.ts:82](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L82) #### Parameters @@ -197,7 +197,7 @@ Defined in: [packages/db/src/indexes/base-index.ts:73](https://github.com/TanSta matchesField: (fieldPath) => boolean; ``` -Defined in: [packages/db/src/indexes/base-index.ts:71](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L71) +Defined in: [packages/db/src/indexes/base-index.ts:80](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L80) #### Parameters @@ -491,6 +491,28 @@ Defined in: [packages/db/src/indexes/base-index.ts:64](https://github.com/TanSta *** +### supportsRangeOptimization + +#### Get Signature + +```ts +get supportsRangeOptimization(): boolean; +``` + +Defined in: [packages/db/src/indexes/base-index.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L78) + +Whether range lookups (gt/gte/lt/lte) on this index can be trusted to +return every matching key. Range traversal relies on the index ordering, so +it is unsafe when the index uses a custom comparator, whose order may not +match the WHERE evaluator's relational operators. Callers must fall back to +a full scan when this is `false`. + +##### Returns + +`boolean` + +*** + ### valueMapData #### Get Signature diff --git a/docs/reference/type-aliases/IndexConstructor.md b/docs/reference/type-aliases/IndexConstructor.md index 0a64c46ab2..304437506f 100644 --- a/docs/reference/type-aliases/IndexConstructor.md +++ b/docs/reference/type-aliases/IndexConstructor.md @@ -9,7 +9,7 @@ title: IndexConstructor type IndexConstructor = (id, expression, name?, options?) => BaseIndex; ``` -Defined in: [packages/db/src/indexes/base-index.ts:213](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L213) +Defined in: [packages/db/src/indexes/base-index.ts:231](https://github.com/TanStack/db/blob/main/packages/db/src/indexes/base-index.ts#L231) Type for index constructor From 2b27dd1448da71c78a48e2390cb71b0ada1b1488 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 26 Jun 2026 11:18:23 -0600 Subject: [PATCH 21/58] fix: reconcile duplicate live query child inserts (#1600) --- .changeset/fix-sync-duplicate-key-utils.md | 5 ++ packages/db/src/collection/sync.ts | 7 +- .../query/live/collection-config-builder.ts | 15 ++-- packages/db/tests/collection.test.ts | 37 +++++++++- packages/db/tests/query/includes.test.ts | 54 ++++++++++++++ .../query-db-collection/tests/query.test.ts | 74 ++++++++++++++++++- 6 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 .changeset/fix-sync-duplicate-key-utils.md diff --git a/.changeset/fix-sync-duplicate-key-utils.md b/.changeset/fix-sync-duplicate-key-utils.md new file mode 100644 index 0000000000..b9d245768a --- /dev/null +++ b/.changeset/fix-sync-duplicate-key-utils.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Fix live query includes reconciliation so updates that re-emit existing child rows update internal child collections instead of attempting duplicate inserts, and ensure duplicate-key sync errors handle collection configs without live query internals. diff --git a/packages/db/src/collection/sync.ts b/packages/db/src/collection/sync.ts index 82fffb7728..af89ed2cf3 100644 --- a/packages/db/src/collection/sync.ts +++ b/packages/db/src/collection/sync.ts @@ -151,9 +151,10 @@ export class CollectionSyncManager< // throwing a duplicate-key error during reconciliation. messageType = `update` } else { - const utils = this.config - .utils as Partial - const internal = utils[LIVE_QUERY_INTERNAL] + const utils = this.config.utils as + | Partial + | undefined + const internal = utils?.[LIVE_QUERY_INTERNAL] throw new DuplicateKeySyncError(key, this.id, { hasCustomGetKey: internal?.hasCustomGetKey ?? false, hasJoins: internal?.hasJoins ?? false, diff --git a/packages/db/src/query/live/collection-config-builder.ts b/packages/db/src/query/live/collection-config-builder.ts index 8faa55265f..c477df152b 100644 --- a/packages/db/src/query/live/collection-config-builder.ts +++ b/packages/db/src/query/live/collection-config-builder.ts @@ -1673,14 +1673,19 @@ function flushIncludesState( if (entry.orderByIndices && change.orderByIndex !== undefined) { entry.orderByIndices.set(change.value, change.orderByIndex) } + const key = entry.syncMethods.collection.getKeyFromItem( + change.value, + ) + const childAlreadyExists = entry.syncMethods.collection.has(key) + if (change.inserts > 0 && change.deletes === 0) { - entry.syncMethods.write({ value: change.value, type: `insert` }) + entry.syncMethods.write({ + value: change.value, + type: childAlreadyExists ? `update` : `insert`, + }) } else if ( change.inserts > change.deletes || - (change.inserts === change.deletes && - entry.syncMethods.collection.has( - entry.syncMethods.collection.getKeyFromItem(change.value), - )) + (change.inserts === change.deletes && childAlreadyExists) ) { entry.syncMethods.write({ value: change.value, type: `update` }) } else if (change.deletes > 0) { diff --git a/packages/db/tests/collection.test.ts b/packages/db/tests/collection.test.ts index 7fc5d67b46..c204b89afe 100644 --- a/packages/db/tests/collection.test.ts +++ b/packages/db/tests/collection.test.ts @@ -4,6 +4,7 @@ import { createCollection } from '../src/collection/index.js' import { CollectionRequiresConfigError, DuplicateKeyError, + DuplicateKeySyncError, InvalidKeyError, KeyUpdateNotAllowedError, MissingDeleteHandlerError, @@ -18,7 +19,12 @@ import { stripVirtualProps, withExpectedRejection, } from './utils' -import type { ChangeMessage, MutationFn, PendingMutation } from '../src/types' +import type { + ChangeMessage, + MutationFn, + PendingMutation, + SyncConfig, +} from '../src/types' const getStateValue = ( collection: { state: Map }, @@ -42,6 +48,35 @@ describe(`Collection`, () => { expect(() => createCollection()).toThrow(CollectionRequiresConfigError) }) + it(`throws DuplicateKeySyncError instead of TypeError when config has no utils`, async () => { + let begin!: () => void + let write!: Parameters< + SyncConfig<{ id: number; text: string }, number>[`sync`] + >[0][`write`] + + const collection = createCollection<{ id: number; text: string }, number>({ + id: `duplicate-key-no-utils-test`, + getKey: (item) => item.id, + sync: { + sync: (params) => { + begin = params.begin + write = params.write + params.begin() + params.write({ type: `insert`, value: { id: 1, text: `one` } }) + params.commit() + params.markReady() + }, + }, + }) + + await collection.stateWhenReady() + + begin() + expect(() => + write({ type: `insert`, value: { id: 1, text: `changed` } }), + ).toThrow(DuplicateKeySyncError) + }) + it(`removes optimistic insert when sync confirms with a different server-generated key`, async () => { const options = mockSyncCollectionOptionsNoInitialState<{ id: number diff --git a/packages/db/tests/query/includes.test.ts b/packages/db/tests/query/includes.test.ts index 0124ebbeac..87bb4ea5b0 100644 --- a/packages/db/tests/query/includes.test.ts +++ b/packages/db/tests/query/includes.test.ts @@ -567,6 +567,60 @@ describe(`includes subqueries`, () => { }) describe(`change propagation`, () => { + it(`Collection includes: joined child update does not duplicate insert into child collection`, async () => { + type LineItem = { id: number; productId: number; qty: number } + type Product = { id: number; categoryId: number; name: string } + + const lineItems = createCollection( + mockSyncCollectionOptions({ + id: `includes-line-items`, + getKey: (lineItem) => lineItem.id, + initialData: [{ id: 1, productId: 10, qty: 1 }], + }), + ) + const products = createCollection( + mockSyncCollectionOptions({ + id: `includes-products`, + getKey: (product) => product.id, + initialData: [{ id: 10, categoryId: 1, name: `Widget` }], + }), + ) + + const collection = createLiveQueryCollection((q) => + q.from({ lineItem: lineItems }).select(({ lineItem }) => ({ + id: lineItem.id, + product: q + .from({ product: products }) + .where(({ product }) => eq(product.id, lineItem.productId)) + .select(({ product }) => ({ + id: product.id, + categoryId: product.categoryId, + name: product.name, + })), + })), + ) + await collection.preload() + + lineItems.utils.begin() + expect(() => { + lineItems.utils.write({ + type: `delete`, + value: { id: 1, productId: 10, qty: 1 }, + }) + lineItems.utils.write({ + type: `insert`, + value: { id: 1, productId: 10, qty: 2 }, + }) + }).not.toThrow() + lineItems.utils.commit() + + await vi.waitFor(() => { + expect(childItems((collection.get(1) as any).product)).toEqual([ + { id: 10, categoryId: 1, name: `Widget` }, + ]) + }) + }) + it(`Collection includes: child change does not re-emit the parent row`, async () => { const collection = buildIncludesQuery() await collection.preload() diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 0c501ab027..b9ccb8f550 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -9,7 +9,10 @@ import { inArray, or, } from '@tanstack/db' -import { stripVirtualProps } from '../../db/tests/utils' +import { + mockSyncCollectionOptions, + stripVirtualProps, +} from '../../db/tests/utils' import { persistedCollectionOptions } from '../../db-sqlite-persistence-core/src' import { queryCollectionOptions } from '../src/query' import type { QueryFunctionContext } from '@tanstack/query-core' @@ -228,6 +231,75 @@ describe(`QueryCollection`, () => { expect(collection._state.syncedData.get(`2`)).toEqual(initialItems[1]) }) + it(`should not duplicate insert into includes child collection after update refetch`, async () => { + type LineItem = { id: string; productId: string } + type Product = { id: string; categoryId: number; name: string } + + const lineItems = createCollection( + mockSyncCollectionOptions({ + id: `query-collection-line-items`, + getKey: (lineItem) => lineItem.id, + initialData: [{ id: `line-1`, productId: `product-1` }], + }), + ) + + let productsData: Array = [ + { id: `product-1`, categoryId: 1, name: `Widget` }, + ] + + const products = createCollection( + queryCollectionOptions({ + id: `query-collection-products`, + queryClient, + queryKey: [`products`], + queryFn: vi + .fn() + .mockImplementation(() => Promise.resolve(productsData)), + getKey: (product) => product.id, + startSync: true, + onUpdate: async ({ transaction }) => { + for (const mutation of transaction.mutations) { + productsData = productsData.map((product) => + product.id === mutation.key ? mutation.modified : product, + ) + } + }, + }), + ) + + const collection = createLiveQueryCollection((q) => + q.from({ lineItem: lineItems }).select(({ lineItem }) => ({ + id: lineItem.id, + product: q + .from({ product: products }) + .where(({ product }) => eq(product.id, lineItem.productId)) + .select(({ product }) => ({ + id: product.id, + categoryId: product.categoryId, + name: product.name, + })), + })), + ) + + await collection.preload() + + expect(() => { + products.update(`product-1`, (draft) => { + draft.categoryId = 2 + }) + }).not.toThrow() + + await vi.waitFor(() => { + expect( + stripVirtualProps((collection.get(`line-1`) as any).product.toArray[0]), + ).toEqual({ + id: `product-1`, + categoryId: 2, + name: `Widget`, + }) + }) + }) + it(`should update collection when query data changes`, async () => { const queryKey = [`testItems`] const initialItems: Array = [ From 928afa4a1bc57b24d174ddd98d128339ede2cc1c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:31:51 +0200 Subject: [PATCH 22/58] ci: Version Packages (#1624) --- .changeset/fix-sync-duplicate-key-utils.md | 5 -- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 7 ++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 6 ++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 59 files changed, 262 insertions(+), 97 deletions(-) delete mode 100644 .changeset/fix-sync-duplicate-key-utils.md diff --git a/.changeset/fix-sync-duplicate-key-utils.md b/.changeset/fix-sync-duplicate-key-utils.md deleted file mode 100644 index b9d245768a..0000000000 --- a/.changeset/fix-sync-duplicate-key-utils.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Fix live query includes reconciliation so updates that re-emit existing child rows update internal child collections instead of attempting duplicate inserts, and ensure duplicate-key sync errors handle collection configs without live query internals. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index e4bec9fd77..51cb7ea35f 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.71", - "@tanstack/db": "^0.6.11", + "@tanstack/angular-db": "^0.1.72", + "@tanstack/db": "^0.6.12", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index dc638b0491..8d5cbb0b78 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.15", - "@tanstack/node-db-sqlite-persistence": "^0.2.3", - "@tanstack/offline-transactions": "^1.0.36", - "@tanstack/query-db-collection": "^1.0.43", - "@tanstack/react-db": "^0.1.89", + "@tanstack/electron-db-sqlite-persistence": "^0.1.16", + "@tanstack/node-db-sqlite-persistence": "^0.2.4", + "@tanstack/offline-transactions": "^1.0.37", + "@tanstack/query-db-collection": "^1.0.44", + "@tanstack/react-db": "^0.1.90", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index b73e0de5ef..754823adf7 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.11", - "@tanstack/offline-transactions": "^1.0.36", - "@tanstack/query-db-collection": "^1.0.43", - "@tanstack/react-db": "^0.1.89", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.3", + "@tanstack/db": "^0.6.12", + "@tanstack/offline-transactions": "^1.0.37", + "@tanstack/query-db-collection": "^1.0.44", + "@tanstack/react-db": "^0.1.90", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.4", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 251e4fb0ee..4ded14d59a 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.11", - "@tanstack/electric-db-collection": "^0.3.9", - "@tanstack/offline-transactions": "^1.0.36", - "@tanstack/react-db": "^0.1.89", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.3", + "@tanstack/db": "^0.6.12", + "@tanstack/electric-db-collection": "^0.3.10", + "@tanstack/offline-transactions": "^1.0.37", + "@tanstack/react-db": "^0.1.90", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.4", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index dbd42fe890..8d9e0174f6 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.3", - "@tanstack/db": "^0.6.11", - "@tanstack/offline-transactions": "^1.0.36", - "@tanstack/query-db-collection": "^1.0.43", - "@tanstack/react-db": "^0.1.89", + "@tanstack/browser-db-sqlite-persistence": "^0.2.4", + "@tanstack/db": "^0.6.12", + "@tanstack/offline-transactions": "^1.0.37", + "@tanstack/query-db-collection": "^1.0.44", + "@tanstack/react-db": "^0.1.90", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index bbba118c04..0b655db848 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.11", - "@tanstack/react-db": "^0.1.89", + "@tanstack/db": "^0.6.12", + "@tanstack/react-db": "^0.1.90", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 6d9ebf69a3..5b86d9397f 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.43", - "@tanstack/react-db": "^0.1.89", + "@tanstack/query-db-collection": "^1.0.44", + "@tanstack/react-db": "^0.1.90", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index c50c4e8f48..fa35da8b2a 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.9", + "@tanstack/electric-db-collection": "^0.3.10", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.43", - "@tanstack/react-db": "^0.1.89", + "@tanstack/query-db-collection": "^1.0.44", + "@tanstack/react-db": "^0.1.90", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.89", + "@tanstack/trailbase-db-collection": "^0.1.90", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index 160da016d8..d1c630256e 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.9", + "@tanstack/electric-db-collection": "^0.3.10", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.43", - "@tanstack/solid-db": "^0.2.25", + "@tanstack/query-db-collection": "^1.0.44", + "@tanstack/solid-db": "^0.2.26", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.89", + "@tanstack/trailbase-db-collection": "^0.1.90", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 379f8cc4ac..a9311a8bbb 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.72 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.1.71 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 860c95cc10..0732ceb0e7 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.71", + "version": "0.1.72", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index 96b233451a..e19f17b756 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 13c5f72813..c7648d8eaa 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.3", + "version": "0.2.4", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 919e68651d..9d9d871066 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index 731515350c..d9bbdac5fd 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.16 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + - @tanstack/capacitor-db-sqlite-persistence@0.2.4 + ## 0.0.15 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 14cfbdb8c5..5efdbeb19d 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.15", + "version": "0.0.16", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 065d73caf5..ce498970bc 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.3", + "version": "0.2.4", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index 9450a149fb..248da3ddb1 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index dac189a3a0..8f1b4704c1 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.3", + "version": "0.2.4", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index 55dedf5638..493036319c 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.4 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.2.3 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 6a13001545..e6cad92c3f 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.3", + "version": "0.2.4", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index 241da16fc4..24e61d7d88 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,11 @@ # @tanstack/db +## 0.6.12 + +### Patch Changes + +- Fix live query includes reconciliation so updates that re-emit existing child rows update internal child collections instead of attempting duplicate inserts, and ensure duplicate-key sync errors handle collection configs without live query internals. ([#1600](https://github.com/TanStack/db/pull/1600)) + ## 0.6.11 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index bb52641e7a..38a0899fe1 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.11", + "version": "0.6.12", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 89b05da7e1..123cd00ff6 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.10 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.3.9 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index 1d216becd7..d347213726 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.9", + "version": "0.3.10", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index c362e9eaed..4a338e854e 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.16 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.1.15 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 72fb5e29e5..3ebf475002 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.15", + "version": "0.1.16", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index 129d9c443f..238aa5e684 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index b013055f00..0afd282d65 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.16 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + - @tanstack/expo-db-sqlite-persistence@0.2.4 + ## 0.0.15 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 8932b4e515..1d2512d18a 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.15", + "version": "0.0.16", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index f2e6e22f35..8e073ce4d9 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.3", + "version": "0.2.4", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index f6a0fd5ed2..d53e303362 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index d82a133a5f..907d6a91d2 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.3", + "version": "0.2.4", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index 6c538f8830..46b8214db0 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.37 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 1.0.36 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index 7f1e7e93d4..ae41b865c8 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.36", + "version": "1.0.37", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index ad6f924787..eabe915067 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.50 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.1.49 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index d2d0a4bf20..e3fc9bf017 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.49", + "version": "0.1.50", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index cfde88b398..e13d824ce0 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.44 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 1.0.43 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index 331682899f..e4ff257275 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.43", + "version": "1.0.44", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index a49980d0d5..5f1df1b021 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.90 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.1.89 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index e4633ffcda..35f2976db2 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.89", + "version": "0.1.90", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 61aa61d6d5..ab2a1e5ab7 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index f2eadb562a..a2748d89d1 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.3", + "version": "0.2.4", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 3c65a86f03..3838244c6e 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.78 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.1.77 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index 3c601b0893..036b383abd 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.77", + "version": "0.1.78", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 5fd08df702..dc19544744 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.26 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.2.25 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index f69f9d54ec..4fb86f09aa 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.25", + "version": "0.2.26", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index ee482232a0..47d69ca0aa 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.89 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.1.88 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index 35f3baeaa4..5a8d7c2fd8 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.88", + "version": "0.1.89", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 14a5883d5f..b0c2f50cde 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 1dc4e1415e..0c2cd6af1b 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.16 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + - @tanstack/tauri-db-sqlite-persistence@0.2.4 + ## 0.0.15 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 0acaf00182..5dc3b0f7a7 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.15", + "version": "0.0.16", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index dfc5a6b9de..769840a599 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.3", + "version": "0.2.4", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index 25015c330e..06abe923a5 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.90 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.1.89 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 8a321eaa64..94a49c1367 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.89", + "version": "0.1.90", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index be960b9861..465ebed48c 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.123 + +### Patch Changes + +- Updated dependencies [[`2b27dd1`](https://github.com/TanStack/db/commit/2b27dd1448da71c78a48e2390cb71b0ada1b1488)]: + - @tanstack/db@0.6.12 + ## 0.0.122 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index 1bc53c538e..b90cba7037 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.122", + "version": "0.0.123", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d45f72454..cfbc4a8ef9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.71 + specifier: ^0.1.72 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.11 + specifier: ^0.6.12 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.15 + specifier: ^0.1.16 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.3 + specifier: ^0.2.4 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.36 + specifier: ^1.0.37 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.43 + specifier: ^1.0.44 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.11 + specifier: ^0.6.12 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.36 + specifier: ^1.0.37 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.43 + specifier: ^1.0.44 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.3 + specifier: ^0.2.4 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.11 + specifier: ^0.6.12 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.9 + specifier: ^0.3.10 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.36 + specifier: ^1.0.37 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.3 + specifier: ^0.2.4 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.3 + specifier: ^0.2.4 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.11 + specifier: ^0.6.12 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.36 + specifier: ^1.0.37 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.43 + specifier: ^1.0.44 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.11 + specifier: ^0.6.12 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.43 + specifier: ^1.0.44 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.9 + specifier: ^0.3.10 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.43 + specifier: ^1.0.44 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.9 + specifier: ^0.3.10 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.43 + specifier: ^1.0.44 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.25 + specifier: ^0.2.26 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.89 + specifier: ^0.1.90 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 99e9afed46ab4083d66609a3e37ee44103c2177f Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 29 Jun 2026 09:58:05 +0200 Subject: [PATCH 23/58] fix(db): preserve discriminated union types through .select() (#1511) (#1597) * fix(db): preserve discriminated union types through .select() (#1511) Distribute Ref over its T parameter so a discriminated union field no longer has its keys collapsed by the mapped-type keyof. Reshape ExtractRef to distinguish a real branded Ref (return the underlying user type U directly) from a spread-produced inline object (still projected via ResultTypeFromSelect). Add DeepNullable so the Nullable=true flag (from left/right/full joins) keeps propagating | undefined into every leaf, preserving prior join-test behavior. Fixes the issue both at the top level and when the union field is nested inside another selected object. * ci: apply automated fixes * fix(db): tighten true-Ref detection to a strict structural match The new `IsTrueRef` fast path only checked that a ref had no keys beyond `keyof U` (plus the brand/virtual props). That key-subset check let spread-derived objects that keep the same key set but change a field's type (`{ ...u, code: u.slug }`) or drop an optional key (`const { nickname, ...rest } = u`) be classified as a true `Ref` and collapsed back to `U`, discarding the projection. Require strict structural equivalence against the canonical `Ref` shape instead, so only genuine refs take the fast path and any spread-derived object falls through to `ResultTypeFromSelect`. Adds regression tests for both cases. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: kevin-dp Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) --- .changeset/fix-ref-union-collapse-b.md | 5 + packages/db/src/query/builder/types.ts | 61 +++++++++++- packages/db/tests/query/select.test-d.ts | 122 ++++++++++++++++++++++- 3 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-ref-union-collapse-b.md diff --git a/.changeset/fix-ref-union-collapse-b.md b/.changeset/fix-ref-union-collapse-b.md new file mode 100644 index 0000000000..05e42450a2 --- /dev/null +++ b/.changeset/fix-ref-union-collapse-b.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Fix `.select()` collapsing discriminated-union fields to the intersection of common keys (#1511). `Ref` now distributes over `T` so `keyof (A | B | C)` no longer reduces the union to its common keys, and `ExtractRef` now distinguishes a real branded `Ref` (where the underlying user type `U` can be returned directly) from a spread-produced inline object (which still needs to be projected through `ResultTypeFromSelect`). This preserves discriminated unions both when the field is selected at the top level and when the field is nested inside another selected object. The real-`Ref` detection uses a strict structural equivalence against the canonical `Ref` shape, so spread-derived objects that keep the same keys but change a field's type (e.g. `{ ...u, code: u.slug }`) or drop an optional key (e.g. `const { nickname, ...rest } = u`) are projected through `ResultTypeFromSelect` instead of being collapsed back to `U`. diff --git a/packages/db/src/query/builder/types.ts b/packages/db/src/query/builder/types.ts index ac6a95dac4..d8b730e76b 100644 --- a/packages/db/src/query/builder/types.ts +++ b/packages/db/src/query/builder/types.ts @@ -478,8 +478,59 @@ type ResultTypeFromCaseWhen = T extends unknown ? ResultTypeFromSelectValue : never -// Extract Ref or subobject with a spread or a Ref -type ExtractRef = Prettify>> +// Extract Ref or subobject with a spread or a Ref. +type ExtractRef = T extends unknown + ? IsTrueRef extends true + ? T extends RefLeaf + ? IsNullableRef extends true + ? DeepNullable + : U + : never + : Prettify>> + : never + +// A "true" Ref is one that is structurally equivalent to the canonical +// `Ref` shape the query builder produces for its underlying user type +// `U` (taking the ref's own nullability into account). When `T` is a true +// ref, `ExtractRef` can safely return `U` directly; otherwise it must fall +// through to the recursive projection. +// +// Checking only that `T` has "no extra keys" beyond `keyof U` (plus the +// brand/virtual props) is not sufficient. A spread-derived object can keep +// exactly the keys of `U` while: +// - changing a field's type, e.g. `{ ...u, code: u.slug }`, or +// - dropping an optional key, e.g. `const { nickname, ...rest } = u`. +// Both must be recursively projected, not collapsed back to `U`. We +// therefore require strict structural equivalence against the canonical ref +// shape rather than a one-directional key-subset check. +type IsTrueRef = + T extends RefLeaf + ? RefShapeMatches>> extends true + ? true + : false + : false + +// Strict structural equivalence between two ref shapes. Unlike plain +// bidirectional assignability, this is sensitive to *key presence* — an +// object that drops an optional key (e.g. `const { nickname, ...rest } = u`) +// is not considered equal to one that keeps `nickname?`, even though the two +// remain mutually assignable. A direct ref (`u.document`, a union member, +// etc.) is exactly the canonical `Ref` shape and matches here, so it returns +// `U` via the fast path; any spread-derived object differs (changed field +// types, dropped keys, or stripped `readonly` modifiers) and instead falls +// through to the recursive projection, which reconstructs the correct type. +type RefShapeMatches = + (() => G extends A ? 1 : 2) extends () => G extends B ? 1 : 2 + ? true + : false + +// Propagate nullable-join semantics into the user-data shape. +type DeepNullable = + T extends Record + ? IsPlainObject extends true + ? { [K in keyof T]: DeepNullable } + : T | undefined + : T | undefined // Helper type to extract the underlying type from various expression types type ExtractExpressionType = @@ -770,7 +821,11 @@ type VirtualPropsRef = { * select(({ user }) => ({ ...user })) // Returns User type, not Ref types * ``` */ -export type Ref = { +export type Ref = T extends unknown + ? RefBranch + : never + +type RefBranch = { [K in keyof T]: IsNonExactOptional extends true ? IsNonExactNullable extends true ? // Both optional and nullable diff --git a/packages/db/tests/query/select.test-d.ts b/packages/db/tests/query/select.test-d.ts index 225decdfb3..c40b3607df 100644 --- a/packages/db/tests/query/select.test-d.ts +++ b/packages/db/tests/query/select.test-d.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, test } from 'vitest' import { createCollection } from '../../src/collection/index.js' -import { createLiveQueryCollection } from '../../src/query/index.js' +import { createLiveQueryCollection, eq } from '../../src/query/index.js' import { mockSyncCollectionOptions } from '../utils.js' import { upper } from '../../src/query/builder/functions.js' import type { OutputWithVirtual } from '../utils.js' @@ -109,6 +109,126 @@ describe(`select types`, () => { expectTypeOf(results).toMatchTypeOf>() }) + test(`select preserves union types and where works on common keys`, () => { + type ItemDocument = + | { type: 'pdf'; url: string; pages: number } + | { type: 'image'; url: string; width: number; height: number } + | { type: 'legacy'; path: string } + + type Item = { id: number; name: string; document: ItemDocument } + + const items = createCollection( + mockSyncCollectionOptions({ + id: `union-field-items`, + getKey: (i) => i.id, + initialData: [], + }), + ) + + // Filtering by a common key of the union should compile, + // and the result should preserve the full discriminated union + const col = createLiveQueryCollection((q) => + q + .from({ i: items }) + .where(({ i }) => eq(i.document.type, `pdf`)) + .select(({ i }) => ({ + id: i.id, + document: i.document, + })), + ) + + const result = col.toArray[0]! + expectTypeOf(result.document).toEqualTypeOf() + }) + + test(`select preserves union when nested under another field`, () => { + type Payload = + | { kind: 'text'; body: string } + | { kind: 'binary'; bytes: number; mime: string } + + type Envelope = { id: number; payload: { inner: Payload } } + + const envelopes = createCollection( + mockSyncCollectionOptions({ + id: `nested-union-envelopes`, + getKey: (e) => e.id, + initialData: [], + }), + ) + + // Selecting a nested object whose field is a discriminated union + // must preserve the union (not collapse to the intersection of keys). + const col = createLiveQueryCollection((q) => + q.from({ e: envelopes }).select(({ e }) => ({ + id: e.id, + payload: e.payload, + })), + ) + const r = col.toArray[0]! + expectTypeOf(r.payload).toEqualTypeOf<{ inner: Payload }>() + expectTypeOf(r.payload.inner).toEqualTypeOf() + }) + + test(`spread with a same-key narrower override projects the override type`, () => { + type SpreadUser = { + id: number + code: string | number + slug: string + nickname?: string + } + + const spreadUsers = createCollection( + mockSyncCollectionOptions({ + id: `spread-override-users`, + getKey: (u) => u.id, + initialData: [], + }), + ) + + const col = createLiveQueryCollection((q) => + q.from({ u: spreadUsers }).select(({ u }) => ({ + narrowed: { ...u, code: u.slug }, + })), + ) + + const result = col.toArray[0]! + // `code` was overridden with `u.slug` (string), so the projected + // field must be `string`, not the original `string | number`. + expectTypeOf(result.narrowed.code).toEqualTypeOf() + }) + + test(`spread that omits an optional property drops the key`, () => { + type SpreadUser = { + id: number + code: string | number + slug: string + nickname?: string + } + + const spreadUsers = createCollection( + mockSyncCollectionOptions({ + id: `spread-omit-users`, + getKey: (u) => u.id, + initialData: [], + }), + ) + + const col = createLiveQueryCollection((q) => + q.from({ u: spreadUsers }).select(({ u }) => { + const { nickname, ...withoutNickname } = u + return { trimmed: withoutNickname } + }), + ) + + const result = col.toArray[0]! + // `nickname` was destructured out, so the projected object must + // not reintroduce the key. + type HasNickname = `nickname` extends keyof typeof result.trimmed + ? true + : false + expectTypeOf().toEqualTypeOf() + }) + test(`nested spread preserves object structure types`, () => { const users = createUsers() const col = createLiveQueryCollection((q) => { From 816b6671c2cc9806715f6e6ed4410b3f4efb5afb Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 29 Jun 2026 14:45:51 +0200 Subject: [PATCH 24/58] fix(db): nested toArray includes drop children when sibling groups share a correlation key (#1501) (#1607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(db): failing repro for nested toArray dropped children (#1501) Adds a failing test reproducing #1501: with three collection levels (products -> priceRanges -> region), when two priceRanges in different parent groups share the same deepest correlation key (regionId === 1), one of the two nested `region` arrays comes back empty. The nested pipeline buffer is shared by reference across per-parent-group states (createPerEntryIncludesStates) and drainNestedBuffers deletes a buffer entry after routing it to the first matching parent group, so the sibling that drains second finds nothing. Note: the minimal repro in the issue does not trigger the bug as written (its dummy `eq(p.id, _.id)` correlation against a single-row anchor with findOne collapses to one product, so the two overlapping siblings never coexist in the output). This test puts both sibling groups in the result so the collision actually occurs. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(db): fan nested toArray includes out to siblings sharing a correlation key (#1501) With 3+ levels of nested toArray includes, when two children in different parent groups shared the same deepest correlation key, only one received the nested rows and the other came back empty. Two compounding causes: - nestedRoutingIndex mapped each nested correlation key to a single parent group (last-writer-wins), and the shared buffer entry was deleted after routing to the first match, so sibling groups sharing the key were dropped. - the nested pipeline does not re-emit already-materialized rows, so a parent group that starts referencing an existing correlation key after the rows were drained (e.g. a sibling inserted after the initial load) saw nothing. Fixes: - nestedRoutingIndex now maps a nested correlation key to a Set of parent groups; drainNestedBuffers fans buffered grandchild changes out to every ready parent group before dropping the buffer entry. - a per-level cumulative snapshot of net-present grandchild rows seeds late-arriving parent groups from what their siblings already received. Routing-index inserts/deletes and parent-delete cleanup are updated to maintain the per-key parent sets. Adds tests covering initial load, a sibling inserted after load, and deleting one of two siblings that share a correlation key. Co-Authored-By: Claude Opus 4.8 (1M context) * ci: apply automated fixes * test(db): cover shared-correlation-key fix for collection and materialize includes The nested-includes routing fix is independent of how each level is materialized. Add regression tests proving sibling parent groups that share a deepest correlation key resolve their grandchildren when the nested levels are left as live Collections (no wrapper) and when wrapped with materialize(), mirroring the existing toArray coverage. Co-Authored-By: Claude Opus 4.8 (1M context) * test(db): cover late-arrival snapshot re-emit in materialize() variant Add a post-load insert assertion to the shared-correlation-key materialize() regression test: inserting a sibling group that references an already-materialized correlation key must be seeded via the cumulative snapshot without disturbing the existing group's nested rows. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(db): refcount nested toArray routes by child key (#1501) The Set routing index introduced by the previous commit collapses multiple child rows in the same parent group that share a nested correlation key into a single route entry. Deleting one such sibling emptied the entry and dropped the whole route, so the surviving sibling stopped receiving grandchild changes (reported by @samwillis). Track the referencing child keys per (nestedKey, parentGroup) so the parent route is only dropped once its last referencing child row is gone. Also fix a routing hole this exposed: an update that changes a child row's nested correlation key (e.g. a price range's regionId) only carries the new key, so the row's stale reference under the old key was never released — a later sibling delete then mis-routed grandchild changes. A per-setup childKey -> nestedKey map records each row's current nested key so updates can release the old reference, scoped per nested setup so a change to one nested include never disturbs another on the same child. Tests cover: same-parent siblings sharing a key with one deleted, an update that changes the nested key followed by a sibling delete, and isolation between two nested includes on the same child row. Co-Authored-By: Claude Opus 4.8 (1M context) * ci: apply automated fixes * test(db): cover two nested includes on the same child sharing a correlation value Two sibling nested toArray includes on the same child row (region and currency on a price range) that correlate on the same value must resolve independently: re-pointing one include's correlation key must not stop the other include from receiving later changes. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(db): scope nested toArray route store per nested include (#1501) The child-key route refcount tracked the removal decision per nested setup, but the route store itself (nestedRoutingIndex / nestedRoutingReverseIndex) was shared across all sibling nested includes at a level. Because the routing key does not encode the include field, two sibling includes that resolve the same correlation value for the same child row (e.g. a price range with regionId === currencyId) collapse into a single child-key set. Removing one include's reference then emptied the shared route and stranded the other include, so a later change no longer reached it. Make nestedRoutingIndex and nestedRoutingReverseIndex arrays indexed by nested setup, mirroring nestedRoutingChildToNested. Each include owns its own route store, so equal correlation values from different includes can no longer remove each other's routes. removeChildKeyFromRoute now takes the per-setup maps, and parent-delete cleanup iterates every setup. Co-Authored-By: Claude Opus 4.8 (1M context) * ci: apply automated fixes * test(db): cover late-arriving sibling nested includes stay reactive * fix(db): clone nested snapshot rows before replay * test(db): cover recursive nested include sibling fanout * ci: apply automated fixes * fix(db): share nested include routes with shared buffers * ci: apply automated fixes * chore: update nested includes changeset --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../nested-toarray-shared-buffer-overlap.md | 9 + .../query/live/collection-config-builder.ts | 411 ++++- packages/db/tests/query/includes.test.ts | 1339 ++++++++++++++++- 3 files changed, 1702 insertions(+), 57 deletions(-) create mode 100644 .changeset/nested-toarray-shared-buffer-overlap.md diff --git a/.changeset/nested-toarray-shared-buffer-overlap.md b/.changeset/nested-toarray-shared-buffer-overlap.md new file mode 100644 index 0000000000..7e0cc5399b --- /dev/null +++ b/.changeset/nested-toarray-shared-buffer-overlap.md @@ -0,0 +1,9 @@ +--- +'@tanstack/db': patch +--- + +fix(db): keep deeply nested includes in sync when sibling groups share nested correlation keys + +Deeply nested includes could drop or stop updating nested rows when sibling parent groups shared the same nested correlation key, especially when one sibling group was inserted after the initial load. Shared nested pipeline buffers were being drained through route state that was scoped too narrowly, so one branch could consume a buffered update before other branches that referenced the same nested row received it. + +Nested route state is now shared at the same scope as the nested buffer and routes updates to every concrete destination branch before clearing the buffer. Snapshot replay still seeds late-arriving sibling groups with already-materialized rows, and recursive pending-change detection ensures deeper routed updates are flushed back up through the result tree. diff --git a/packages/db/src/query/live/collection-config-builder.ts b/packages/db/src/query/live/collection-config-builder.ts index c477df152b..a6a51b4788 100644 --- a/packages/db/src/query/live/collection-config-builder.ts +++ b/packages/db/src/query/live/collection-config-builder.ts @@ -909,8 +909,6 @@ export class CollectionConfigBuilder< entry.childCompilationResult.includes, syncState, ) - state.nestedRoutingIndex = new Map() - state.nestedRoutingReverseIndex = new Map() } return state @@ -1152,6 +1150,28 @@ function createOrderByComparator( } } +type SnapshotRow = { + value: any + orderByIndex: string | undefined + /** Net multiplicity (inserts − deletes) currently materialized for this row */ + count: number +} + +type NestedRouteIndex = Map< + unknown, + Map>> +> + +type NestedRouteReverseIndex = Map< + IncludesOutputState, + Map> +> + +type NestedRouteChildToNested = Map< + IncludesOutputState, + Map> +> + /** * Shared buffer setup for a single nested includes level. * Pipeline output writes into the buffer; during flush the buffer is drained @@ -1161,6 +1181,23 @@ type NestedIncludesSetup = { compilationResult: IncludesCompilationResult /** Shared buffer: nestedCorrelationKey → Map */ buffer: Map>> + /** + * Cumulative net-present grandchild rows per nested correlation key. The + * buffer holds only deltas since the last drain and is cleared once drained, + * so a parent group that starts referencing an existing correlation key + * *after* the rows were already drained (the pipeline does not re-emit them) + * would otherwise see nothing. The snapshot lets such late-arriving parent + * groups be seeded with the rows their siblings already received. + */ + snapshot: Map> + /** + * Shared route store for this shared buffer. Routes target concrete + * IncludesOutputState instances so one emitted child row can fan out across + * every per-entry state that references the same nested correlation key. + */ + routingIndex: NestedRouteIndex + routingReverseIndex: NestedRouteReverseIndex + routingChildToNested: NestedRouteChildToNested /** For 3+ levels of nesting */ nestedSetups?: Array } @@ -1186,10 +1223,6 @@ type IncludesOutputState = { correlationToParentKeys: Map> /** Shared nested pipeline setups (one per nested includes level) */ nestedSetups?: Array - /** nestedCorrelationKey → parentCorrelationKey */ - nestedRoutingIndex?: Map - /** parentCorrelationKey → Set */ - nestedRoutingReverseIndex?: Map> } type ChildCollectionEntry = { @@ -1300,6 +1333,10 @@ function setupNestedPipelines( const setup: NestedIncludesSetup = { compilationResult: entry, buffer, + snapshot: new Map(), + routingIndex: new Map(), + routingReverseIndex: new Map(), + routingChildToNested: new Map(), } // Recursively set up deeper levels @@ -1336,14 +1373,99 @@ function createPerEntryIncludesStates( if (setup.nestedSetups) { state.nestedSetups = setup.nestedSetups - state.nestedRoutingIndex = new Map() - state.nestedRoutingReverseIndex = new Map() } return state }) } +function cloneSnapshotValue(value: T): T { + if (value == null || typeof value !== `object`) { + return value + } + + return (Array.isArray(value) ? [...value] : { ...value }) as T +} + +/** + * Folds a drained delta into a nested setup's cumulative snapshot, tracking the + * net multiplicity per child row and dropping rows (and empty keys) once their + * net count reaches zero. + */ +function accumulateSnapshot( + setup: NestedIncludesSetup, + nestedCorrelationKey: unknown, + childChanges: Map>, +): void { + let snap = setup.snapshot.get(nestedCorrelationKey) + if (!snap) { + snap = new Map() + setup.snapshot.set(nestedCorrelationKey, snap) + } + + for (const [childKey, changes] of childChanges) { + let row = snap.get(childKey) + if (!row) { + row = { + value: cloneSnapshotValue(changes.value), + orderByIndex: changes.orderByIndex, + count: 0, + } + snap.set(childKey, row) + } + row.count += changes.inserts - changes.deletes + if (changes.inserts > 0) { + row.value = cloneSnapshotValue(changes.value) + if (changes.orderByIndex !== undefined) { + row.orderByIndex = changes.orderByIndex + } + } + if (row.count <= 0) { + snap.delete(childKey) + } + } + + if (snap.size === 0) { + setup.snapshot.delete(nestedCorrelationKey) + } +} + +/** + * Seeds a parent group's per-entry state with the rows already materialized for + * a nested correlation key. Used when a parent group starts referencing a key + * whose rows were drained (and cleared from the buffer) in an earlier flush, so + * the pipeline will not re-emit them. + */ +function seedParentFromSnapshot( + state: IncludesOutputState, + setupIndex: number, + parentCorrelationKey: unknown, + nestedCorrelationKey: unknown, +): void { + const setup = state.nestedSetups![setupIndex]! + const snap = setup.snapshot.get(nestedCorrelationKey) + if (!snap || snap.size === 0) return + + const entry = state.childRegistry.get(parentCorrelationKey) + if (!entry || !entry.includesStates) return + + const entryState = entry.includesStates[setupIndex]! + let byChild = entryState.pendingChildChanges.get(nestedCorrelationKey) + if (!byChild) { + byChild = new Map() + entryState.pendingChildChanges.set(nestedCorrelationKey, byChild) + } + for (const [childKey, row] of snap) { + if (byChild.has(childKey)) continue + byChild.set(childKey, { + deletes: 0, + inserts: row.count, + value: cloneSnapshotValue(row.value), + orderByIndex: row.orderByIndex, + }) + } +} + /** * Drains shared buffers into per-entry states using the routing index. * Returns the set of parent correlation keys that had changes routed to them. @@ -1353,48 +1475,68 @@ function drainNestedBuffers(state: IncludesOutputState): Set { if (!state.nestedSetups) return dirtyCorrelationKeys - for (let i = 0; i < state.nestedSetups.length; i++) { - const setup = state.nestedSetups[i]! + for (const setup of state.nestedSetups) { const toDelete: Array = [] for (const [nestedCorrelationKey, childChanges] of setup.buffer) { - const parentCorrelationKey = - state.nestedRoutingIndex!.get(nestedCorrelationKey) - if (parentCorrelationKey === undefined) { + const stateRoutes = setup.routingIndex.get(nestedCorrelationKey) + if (stateRoutes === undefined || stateRoutes.size === 0) { // Unroutable — parent not yet seen; keep in buffer continue } - const entry = state.childRegistry.get(parentCorrelationKey) - if (!entry || !entry.includesStates) { - continue - } + // A single nested correlation key can map to multiple parent groups when + // sibling parents share the same correlation value, and at depth 4+ those + // parents may live in different per-entry states. Fan the buffered changes + // out to every ready target before clearing the shared buffer entry. + let routedToAny = false + for (const [targetState, parentRoutes] of stateRoutes) { + const targetSetupIndex = targetState.nestedSetups?.indexOf(setup) ?? -1 + if (targetSetupIndex < 0) continue + + for (const parentCorrelationKey of parentRoutes.keys()) { + const entry = targetState.childRegistry.get(parentCorrelationKey) + if (!entry || !entry.includesStates) { + continue + } - // Route changes into this entry's per-entry state at position i - const entryState = entry.includesStates[i]! - for (const [childKey, changes] of childChanges) { - let byChild = entryState.pendingChildChanges.get(nestedCorrelationKey) - if (!byChild) { - byChild = new Map() - entryState.pendingChildChanges.set(nestedCorrelationKey, byChild) - } - const existing = byChild.get(childKey) - if (existing) { - existing.inserts += changes.inserts - existing.deletes += changes.deletes - if (changes.inserts > 0) { - existing.value = changes.value - if (changes.orderByIndex !== undefined) { - existing.orderByIndex = changes.orderByIndex + // Route changes into this entry's per-entry state at the same setup. + const entryState = entry.includesStates[targetSetupIndex]! + for (const [childKey, changes] of childChanges) { + let byChild = + entryState.pendingChildChanges.get(nestedCorrelationKey) + if (!byChild) { + byChild = new Map() + entryState.pendingChildChanges.set(nestedCorrelationKey, byChild) + } + const existing = byChild.get(childKey) + if (existing) { + existing.inserts += changes.inserts + existing.deletes += changes.deletes + if (changes.inserts > 0) { + existing.value = changes.value + if (changes.orderByIndex !== undefined) { + existing.orderByIndex = changes.orderByIndex + } + } + } else { + byChild.set(childKey, { ...changes }) } } - } else { - byChild.set(childKey, { ...changes }) + + if (targetState === state) { + dirtyCorrelationKeys.add(parentCorrelationKey) + } + routedToAny = true } } - dirtyCorrelationKeys.add(parentCorrelationKey) - toDelete.push(nestedCorrelationKey) + if (routedToAny) { + // Fold the drained delta into the cumulative snapshot so a parent group + // that starts referencing this nested key later can be seeded with it. + accumulateSnapshot(setup, nestedCorrelationKey, childChanges) + toDelete.push(nestedCorrelationKey) + } } for (const key of toDelete) { @@ -1410,6 +1552,52 @@ function drainNestedBuffers(state: IncludesOutputState): Set { * Maps nested correlation keys to parent correlation keys so that * grandchild changes can be routed to the correct per-entry state. */ +/** + * Removes a single child row's reference to a nested routing key from a parent + * group's route, dropping the parent (and the nested key, and the reverse-index + * entry) once no child row in the group references the key anymore. + */ +function removeChildKeyFromRoute( + setup: NestedIncludesSetup, + state: IncludesOutputState, + correlationKey: unknown, + nestedRoutingKey: unknown, + childKey: unknown, +): void { + const stateRoutes = setup.routingIndex.get(nestedRoutingKey) + const parents = stateRoutes?.get(state) + const childKeys = parents?.get(correlationKey) + if (!parents || !childKeys) return + + childKeys.delete(childKey) + // Only drop the parent group from the route once its last child row + // referencing this nested key is gone — a surviving sibling in the same + // parent group must keep receiving grandchild changes. + if (childKeys.size === 0) { + parents.delete(correlationKey) + if (parents.size === 0) { + stateRoutes!.delete(state) + if (stateRoutes!.size === 0) { + setup.routingIndex.delete(nestedRoutingKey) + } + } + // The reverse index tracks parent → nested keys at group granularity, so + // only drop the entry when no child row in this parent group references the + // nested key anymore. + const reverse = setup.routingReverseIndex.get(state) + const reverseSet = reverse?.get(correlationKey) + if (reverseSet) { + reverseSet.delete(nestedRoutingKey) + if (reverseSet.size === 0) { + reverse!.delete(correlationKey) + if (reverse!.size === 0) { + setup.routingReverseIndex.delete(state) + } + } + } + } +} + function updateRoutingIndex( state: IncludesOutputState, correlationKey: unknown, @@ -1417,8 +1605,14 @@ function updateRoutingIndex( ): void { if (!state.nestedSetups) return - for (const setup of state.nestedSetups) { - for (const [, change] of childChanges) { + for (let i = 0; i < state.nestedSetups.length; i++) { + const setup = state.nestedSetups[i]! + let childToNested = setup.routingChildToNested.get(state) + if (!childToNested) { + childToNested = new Map() + setup.routingChildToNested.set(state, childToNested) + } + for (const [childKey, change] of childChanges) { if (change.inserts > 0) { // Read the nested routing key from the INCLUDES_ROUTING stamp. // Must use the composite routing key (not raw correlationKey) to match @@ -1432,14 +1626,83 @@ function updateRoutingIndex( nestedParentContext, ) + // An update (inserts > 0 && deletes > 0) can change a child row's nested + // correlation key (e.g. a price range's regionId changes). The change + // only carries the NEW key, so drop the row's previous reference for + // THIS setup using the recorded mapping before re-routing it. + // + // This relies on the compiler stamping the FULL INCLUDES_ROUTING map on + // every emitted row (one entry per nested include field), so for an + // unrelated nested include the recomputed nestedRoutingKey equals the + // recorded one and the guard below is a no-op — a change to one nested + // include never disturbs the recorded key of another on the same row. + const perParent = childToNested.get(correlationKey) + const prevNestedKey = perParent?.get(childKey) + if (prevNestedKey !== undefined && prevNestedKey !== nestedRoutingKey) { + removeChildKeyFromRoute( + setup, + state, + correlationKey, + prevNestedKey, + childKey, + ) + perParent!.delete(childKey) + } + if (nestedCorrelationKey != null) { - state.nestedRoutingIndex!.set(nestedRoutingKey, correlationKey) - let reverseSet = state.nestedRoutingReverseIndex!.get(correlationKey) + let stateRoutes = setup.routingIndex.get(nestedRoutingKey) + if (!stateRoutes) { + stateRoutes = new Map() + setup.routingIndex.set(nestedRoutingKey, stateRoutes) + } + let parents = stateRoutes.get(state) + if (!parents) { + parents = new Map() + stateRoutes.set(state, parents) + } + let childKeys = parents.get(correlationKey) + // The parent group is "new" for this nested key only when no child row + // in it referenced the key before; that's the case that needs seeding. + const isNewParent = !childKeys || childKeys.size === 0 + if (!childKeys) { + childKeys = new Set() + parents.set(correlationKey, childKeys) + } + childKeys.add(childKey) + let reverse = setup.routingReverseIndex.get(state) + if (!reverse) { + reverse = new Map() + setup.routingReverseIndex.set(state, reverse) + } + let reverseSet = reverse.get(correlationKey) if (!reverseSet) { reverseSet = new Set() - state.nestedRoutingReverseIndex!.set(correlationKey, reverseSet) + reverse.set(correlationKey, reverseSet) } reverseSet.add(nestedRoutingKey) + + // Record the row's current nested key for this setup so a later update + // that changes it can release the old reference. Reuse perParent when + // it already exists to avoid a second lookup. + let recorded = perParent + if (!recorded) { + recorded = new Map() + childToNested.set(correlationKey, recorded) + } + recorded.set(childKey, nestedRoutingKey) + + // If this parent group is newly associated with a nested key whose + // rows were already drained (and cleared from the buffer) in an + // earlier flush, the pipeline will not re-emit them. Seed this parent + // from the cumulative snapshot so it receives the same rows its + // siblings already have. + if (isNewParent) { + seedParentFromSnapshot(state, i, correlationKey, nestedRoutingKey) + } + } else if (perParent && perParent.size === 0) { + // The row no longer has a nested key (cleared via update) and held no + // others — drop the now-empty per-parent record. + childToNested.delete(correlationKey) } } else if (change.deletes > 0 && change.inserts === 0) { // Remove from routing index @@ -1453,15 +1716,18 @@ function updateRoutingIndex( ) if (nestedCorrelationKey != null) { - state.nestedRoutingIndex!.delete(nestedRoutingKey) - const reverseSet = - state.nestedRoutingReverseIndex!.get(correlationKey) - if (reverseSet) { - reverseSet.delete(nestedRoutingKey) - if (reverseSet.size === 0) { - state.nestedRoutingReverseIndex!.delete(correlationKey) - } - } + removeChildKeyFromRoute( + setup, + state, + correlationKey, + nestedRoutingKey, + childKey, + ) + } + const perParent = childToNested.get(correlationKey) + if (perParent) { + perParent.delete(childKey) + if (perParent.size === 0) childToNested.delete(correlationKey) } } } @@ -1476,14 +1742,40 @@ function cleanRoutingIndexOnDelete( state: IncludesOutputState, correlationKey: unknown, ): void { - if (!state.nestedRoutingReverseIndex) return + if (!state.nestedSetups) return - const nestedKeys = state.nestedRoutingReverseIndex.get(correlationKey) - if (nestedKeys) { + // The whole parent group is gone, so drop it from every nested setup's route + // (along with all the child keys it tracked); other sibling parent groups may + // still reference the same nested correlation key. + for (const setup of state.nestedSetups) { + const reverseIndex = setup.routingReverseIndex.get(state) + const nestedKeys = reverseIndex?.get(correlationKey) + if (!nestedKeys) continue for (const nestedKey of nestedKeys) { - state.nestedRoutingIndex!.delete(nestedKey) + const stateRoutes = setup.routingIndex.get(nestedKey) + const parents = stateRoutes?.get(state) + if (parents) { + parents.delete(correlationKey) + if (parents.size === 0) { + stateRoutes!.delete(state) + if (stateRoutes!.size === 0) { + setup.routingIndex.delete(nestedKey) + } + } + } + } + reverseIndex!.delete(correlationKey) + if (reverseIndex!.size === 0) { + setup.routingReverseIndex.delete(state) + } + + const childToNested = setup.routingChildToNested.get(state) + if (childToNested) { + childToNested.delete(correlationKey) + if (childToNested.size === 0) { + setup.routingChildToNested.delete(state) + } } - state.nestedRoutingReverseIndex.delete(correlationKey) } } @@ -1855,6 +2147,13 @@ function hasPendingIncludesChanges( if (state.pendingChildChanges.size > 0) return true if (state.nestedSetups && hasNestedBufferChanges(state.nestedSetups)) return true + for (const entry of state.childRegistry.values()) { + if ( + entry.includesStates && + hasPendingIncludesChanges(entry.includesStates) + ) + return true + } } return false } diff --git a/packages/db/tests/query/includes.test.ts b/packages/db/tests/query/includes.test.ts index 87bb4ea5b0..2eba6f122a 100644 --- a/packages/db/tests/query/includes.test.ts +++ b/packages/db/tests/query/includes.test.ts @@ -12,7 +12,11 @@ import { import { createCollection } from '../../src/collection/index.js' import { CleanupQueue } from '../../src/collection/cleanup-queue.js' import { localOnlyCollectionOptions } from '../../src/local-only.js' -import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js' +import { + flushPromises, + mockSyncCollectionOptions, + stripVirtualProps, +} from '../utils.js' import type { SyncConfig } from '../../src/types.js' type Project = { @@ -4956,6 +4960,1339 @@ describe(`includes subqueries`, () => { expect(data().runs[0].texts).toBe(run1TextsBefore) }) + + // Three collection levels (products -> priceRanges -> region). When two + // price ranges in different parent groups point at the same deepest + // correlation key (regionId 1, one under each product), each must still + // resolve its own copy of the nested `region` array. + it(`resolves nested grandchildren for sibling groups sharing a correlation key`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-price-ranges`, + getKey: (r) => r.id, + initialData: [ + { id: 1, productId: 1, regionId: 1 }, + { id: 2, productId: 1, regionId: 2 }, + { id: 3, productId: 2, regionId: 1 }, // same regionId as priceRange 1 + ], + }), + ) + + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-regions`, + getKey: (r) => r.id, + initialData: [ + { id: 1, name: `Europe` }, + { id: 2, name: `North America` }, + ], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + + await collection.preload() + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { + id: 1, + regionId: 1, + region: [{ id: 1, name: `Europe` }], + }, + { + id: 2, + regionId: 2, + region: [{ id: 2, name: `North America` }], + }, + ], + }, + { + id: 2, + title: `Hoodie`, + priceRanges: [ + { + id: 3, + regionId: 1, + region: [{ id: 1, name: `Europe` }], + }, + ], + }, + ]) + }) + + // When a second parent group starts referencing a deepest correlation key + // that another group already resolved (the sibling price range is inserted + // after the initial load), the newly inserted group must also receive the + // nested grandchildren. + it(`fans nested grandchildren out to a sibling group inserted after load`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-incremental-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-incremental-price-ranges`, + getKey: (r) => r.id, + initialData: [{ id: 1, productId: 1, regionId: 1 }], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-incremental-regions`, + getKey: (r) => r.id, + initialData: [{ id: 1, name: `Europe` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-incremental-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + await collection.preload() + + // Insert a second price range under a different product, sharing regionId 1. + priceRanges.insert({ id: 3, productId: 2, regionId: 1 }) + await new Promise((r) => setTimeout(r, 50)) + + const tree = toTree(collection) + const tshirt = tree.find((p: any) => p.title === `T-Shirt`) + const hoodie = tree.find((p: any) => p.title === `Hoodie`) + expect(tshirt.priceRanges.find((pr: any) => pr.id === 1).region).toEqual([ + { id: 1, name: `Europe` }, + ]) + expect(hoodie.priceRanges.find((pr: any) => pr.id === 3).region).toEqual([ + { id: 1, name: `Europe` }, + ]) + }) + + it(`keeps deeper nested includes reactive for a sibling group added after load`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string; countryId: number } + type Country = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-late-sibling-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-late-sibling-price-ranges`, + getKey: (r) => r.id, + initialData: [{ id: 1, productId: 1, regionId: 1 }], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-late-sibling-regions`, + getKey: (r) => r.id, + initialData: [{ id: 1, name: `Europe`, countryId: 1 }], + }), + ) + const countries = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-late-sibling-countries`, + getKey: (c) => c.id, + initialData: [{ id: 1, name: `France` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + countries.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-late-sibling-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ + id: r.id, + name: r.name, + country: toArray( + q + .from({ c: countries }) + .where(({ c }) => eq(c.id, r.countryId)) + .select(({ c }) => ({ id: c.id, name: c.name })), + ), + })), + ), + })), + ), + })), + }) + await collection.preload() + + priceRanges.insert({ id: 2, productId: 2, regionId: 1 }) + await flushPromises() + + priceRanges.delete(1) + await flushPromises() + + countries.update(1, (draft) => { + draft.name = `Renamed France` + }) + await flushPromises() + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [], + }, + { + id: 2, + title: `Hoodie`, + priceRanges: [ + { + id: 2, + regionId: 1, + region: [ + { + id: 1, + name: `Europe`, + country: [{ id: 1, name: `Renamed France` }], + }, + ], + }, + ], + }, + ]) + }) + + it(`keeps shared nested includes reactive for sibling groups added after load at depth 3`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-3-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-3-price-ranges`, + getKey: (r) => r.id, + initialData: [{ id: 1, productId: 1, regionId: 1 }], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-3-regions`, + getKey: (r) => r.id, + initialData: [{ id: 1, name: `Europe` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-depth-3-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + await collection.preload() + + priceRanges.insert({ id: 2, productId: 2, regionId: 1 }) + await flushPromises() + + regions.update(1, (draft) => { + draft.name = `Renamed Europe` + }) + await flushPromises() + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [{ id: 1, region: [{ id: 1, name: `Renamed Europe` }] }], + }, + { + id: 2, + title: `Hoodie`, + priceRanges: [{ id: 2, region: [{ id: 1, name: `Renamed Europe` }] }], + }, + ]) + }) + + it(`keeps shared nested includes reactive for sibling groups added after load at depth 4`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string; countryId: number } + type Country = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-4-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-4-price-ranges`, + getKey: (r) => r.id, + initialData: [{ id: 1, productId: 1, regionId: 1 }], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-4-regions`, + getKey: (r) => r.id, + initialData: [{ id: 1, name: `Europe`, countryId: 1 }], + }), + ) + const countries = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-4-countries`, + getKey: (c) => c.id, + initialData: [{ id: 1, name: `France` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + countries.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-depth-4-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ + id: r.id, + name: r.name, + country: toArray( + q + .from({ c: countries }) + .where(({ c }) => eq(c.id, r.countryId)) + .select(({ c }) => ({ id: c.id, name: c.name })), + ), + })), + ), + })), + ), + })), + }) + await collection.preload() + + priceRanges.insert({ id: 2, productId: 2, regionId: 1 }) + await flushPromises() + + countries.update(1, (draft) => { + draft.name = `Renamed France` + }) + await flushPromises() + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { + id: 1, + region: [ + { + id: 1, + name: `Europe`, + country: [{ id: 1, name: `Renamed France` }], + }, + ], + }, + ], + }, + { + id: 2, + title: `Hoodie`, + priceRanges: [ + { + id: 2, + region: [ + { + id: 1, + name: `Europe`, + country: [{ id: 1, name: `Renamed France` }], + }, + ], + }, + ], + }, + ]) + }) + + it(`keeps shared nested includes reactive for sibling groups added after load at depth 5`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string; countryId: number } + type Country = { id: number; name: string; zoneId: number } + type Zone = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-5-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-5-price-ranges`, + getKey: (r) => r.id, + initialData: [{ id: 1, productId: 1, regionId: 1 }], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-5-regions`, + getKey: (r) => r.id, + initialData: [{ id: 1, name: `Europe`, countryId: 1 }], + }), + ) + const countries = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-5-countries`, + getKey: (c) => c.id, + initialData: [{ id: 1, name: `France`, zoneId: 1 }], + }), + ) + const zones = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-depth-5-zones`, + getKey: (z) => z.id, + initialData: [{ id: 1, name: `Eurozone` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + countries.preload(), + zones.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-depth-5-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ + id: r.id, + name: r.name, + country: toArray( + q + .from({ c: countries }) + .where(({ c }) => eq(c.id, r.countryId)) + .select(({ c }) => ({ + id: c.id, + name: c.name, + zone: toArray( + q + .from({ z: zones }) + .where(({ z }) => eq(z.id, c.zoneId)) + .select(({ z }) => ({ + id: z.id, + name: z.name, + })), + ), + })), + ), + })), + ), + })), + ), + })), + }) + await collection.preload() + + priceRanges.insert({ id: 2, productId: 2, regionId: 1 }) + await flushPromises() + + zones.update(1, (draft) => { + draft.name = `Renamed Eurozone` + }) + await flushPromises() + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { + id: 1, + region: [ + { + id: 1, + name: `Europe`, + country: [ + { + id: 1, + name: `France`, + zone: [{ id: 1, name: `Renamed Eurozone` }], + }, + ], + }, + ], + }, + ], + }, + { + id: 2, + title: `Hoodie`, + priceRanges: [ + { + id: 2, + region: [ + { + id: 1, + name: `Europe`, + country: [ + { + id: 1, + name: `France`, + zone: [{ id: 1, name: `Renamed Eurozone` }], + }, + ], + }, + ], + }, + ], + }, + ]) + }) + + // When two parent groups share a deepest correlation key and one of them is + // deleted, the surviving group must keep its nested grandchildren. + it(`resolves two nested includes on the same child independently when they share a correlation value`, async () => { + type Product = { id: number; title: string } + type PriceRange = { + id: number + productId: number + regionId: number + currencyId: number + } + type Region = { id: number; name: string } + type Currency = { id: number; code: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-value-products`, + getKey: (p) => p.id, + initialData: [{ id: 1, title: `T-Shirt` }], + }), + ) + // The price range points at region 1 and currency 1: both nested includes + // correlate on the same value. + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-value-price-ranges`, + getKey: (r) => r.id, + initialData: [{ id: 1, productId: 1, regionId: 1, currencyId: 1 }], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-value-regions`, + getKey: (r) => r.id, + initialData: [ + { id: 1, name: `Europe` }, + { id: 2, name: `North America` }, + ], + }), + ) + const currencies = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-value-currencies`, + getKey: (c) => c.id, + initialData: [{ id: 1, code: `EUR` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + currencies.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-value-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + currency: toArray( + q + .from({ c: currencies }) + .where(({ c }) => eq(c.id, pr.currencyId)) + .select(({ c }) => ({ id: c.id, code: c.code })), + ), + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + await collection.preload() + + // Re-point only the region include; the currency include still resolves 1. + priceRanges.update(1, (draft) => { + draft.regionId = 2 + }) + await new Promise((r) => setTimeout(r, 50)) + + // A later currency change must still reach the currency include. + currencies.update(1, (draft) => { + draft.code = `USD` + }) + await new Promise((r) => setTimeout(r, 50)) + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { + id: 1, + currency: [{ id: 1, code: `USD` }], + region: [{ id: 2, name: `North America` }], + }, + ], + }, + ]) + }) + + it(`isolates a nested correlation-key update from a second nested include on the same child`, async () => { + type Product = { id: number; title: string } + type PriceRange = { + id: number + productId: number + regionId: number + currencyId: number + } + type Region = { id: number; name: string } + type Currency = { id: number; code: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `temp2-products`, + getKey: (p) => p.id, + initialData: [{ id: 1, title: `T-Shirt` }], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `temp2-price-ranges`, + getKey: (r) => r.id, + initialData: [{ id: 1, productId: 1, regionId: 1, currencyId: 9 }], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `temp2-regions`, + getKey: (r) => r.id, + initialData: [ + { id: 1, name: `Europe` }, + { id: 2, name: `North America` }, + ], + }), + ) + const currencies = createCollection( + localOnlyCollectionOptions({ + id: `temp2-currencies`, + getKey: (c) => c.id, + initialData: [{ id: 9, code: `EUR` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + currencies.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `temp2-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + currency: toArray( + q + .from({ c: currencies }) + .where(({ c }) => eq(c.id, pr.currencyId)) + .select(({ c }) => ({ id: c.id, code: c.code })), + ), + })), + ), + })), + }) + await collection.preload() + + // Change ONLY regionId; currency must still resolve, and a later currency + // rename must still reach this price range. + priceRanges.update(1, (draft) => { + draft.regionId = 2 + }) + await new Promise((r) => setTimeout(r, 50)) + + currencies.update(9, (draft) => { + draft.code = `USD` + }) + await new Promise((r) => setTimeout(r, 50)) + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { + id: 1, + region: [{ id: 2, name: `North America` }], + currency: [{ id: 9, code: `USD` }], + }, + ], + }, + ]) + }) + + it(`keeps the survivor's data when a child changes its nested key then a sibling sharing the old key is deleted`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `temp-upd-products`, + getKey: (p) => p.id, + initialData: [{ id: 1, title: `T-Shirt` }], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `temp-upd-price-ranges`, + getKey: (r) => r.id, + initialData: [ + { id: 1, productId: 1, regionId: 1 }, + { id: 2, productId: 1, regionId: 1 }, + ], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `temp-upd-regions`, + getKey: (r) => r.id, + initialData: [ + { id: 1, name: `Europe` }, + { id: 2, name: `North America` }, + ], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `temp-upd-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + await collection.preload() + + // pr_1 moves from region 1 to region 2 (both pr_1, pr_2 started at region 1) + priceRanges.update(1, (draft) => { + draft.regionId = 2 + }) + await new Promise((r) => setTimeout(r, 50)) + + // delete pr_2 (the remaining referencer of region 1) + priceRanges.delete(2) + await new Promise((r) => setTimeout(r, 50)) + + // rename region 1 — nothing references it anymore, must NOT affect pr_1 + regions.update(1, (draft) => { + draft.name = `Renamed Europe` + }) + await new Promise((r) => setTimeout(r, 50)) + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { id: 1, regionId: 2, region: [{ id: 2, name: `North America` }] }, + ], + }, + ]) + }) + + it(`keeps grandchildren on the surviving sibling after the other is deleted`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-delete-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-delete-price-ranges`, + getKey: (r) => r.id, + initialData: [ + { id: 1, productId: 1, regionId: 1 }, + { id: 3, productId: 2, regionId: 1 }, + ], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-delete-regions`, + getKey: (r) => r.id, + initialData: [{ id: 1, name: `Europe` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-delete-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + await collection.preload() + + // Delete the Hoodie's price range (the sibling sharing regionId 1). + priceRanges.delete(3) + await new Promise((r) => setTimeout(r, 50)) + + const tree = toTree(collection) + const tshirt = tree.find((p: any) => p.title === `T-Shirt`) + const hoodie = tree.find((p: any) => p.title === `Hoodie`) + expect(tshirt.priceRanges.find((pr: any) => pr.id === 1).region).toEqual([ + { id: 1, name: `Europe` }, + ]) + expect(hoodie.priceRanges).toEqual([]) + }) + + it(`keeps routing when one of multiple same-parent siblings sharing a nested key is deleted`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-same-parent-products`, + getKey: (p) => p.id, + initialData: [{ id: 1, title: `T-Shirt` }], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-same-parent-price-ranges`, + getKey: (r) => r.id, + initialData: [ + { id: 1, productId: 1, regionId: 1 }, + { id: 2, productId: 1, regionId: 1 }, + ], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-same-parent-regions`, + getKey: (r) => r.id, + initialData: [{ id: 1, name: `Europe` }], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-same-parent-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: toArray( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: toArray( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + await collection.preload() + + priceRanges.delete(1) + await new Promise((r) => setTimeout(r, 50)) + + regions.update(1, (draft) => { + draft.name = `Renamed Europe` + }) + await new Promise((r) => setTimeout(r, 50)) + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { + id: 2, + regionId: 1, + region: [{ id: 1, name: `Renamed Europe` }], + }, + ], + }, + ]) + }) + + // The shared-correlation-key routing is independent of how each level is + // materialized, so the same guarantee must hold when the nested levels are + // left as live Collections (no toArray/materialize wrapper). + it(`resolves nested grandchildren for sibling groups when levels stay Collections`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-collection-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-collection-price-ranges`, + getKey: (r) => r.id, + initialData: [ + { id: 1, productId: 1, regionId: 1 }, + { id: 2, productId: 1, regionId: 2 }, + { id: 3, productId: 2, regionId: 1 }, + ], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-collection-regions`, + getKey: (r) => r.id, + initialData: [ + { id: 1, name: `Europe` }, + { id: 2, name: `North America` }, + ], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-collection-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + })), + })), + }) + await collection.preload() + + // toTree recursively unwraps the nested live Collections into arrays. + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { id: 1, regionId: 1, region: [{ id: 1, name: `Europe` }] }, + { + id: 2, + regionId: 2, + region: [{ id: 2, name: `North America` }], + }, + ], + }, + { + id: 2, + title: `Hoodie`, + priceRanges: [ + { id: 3, regionId: 1, region: [{ id: 1, name: `Europe` }] }, + ], + }, + ]) + }) + + // Same guarantee for materialize(), which produces array/singleton + // snapshots through the same nested-includes routing. + it(`resolves nested grandchildren for sibling groups with materialize()`, async () => { + type Product = { id: number; title: string } + type PriceRange = { id: number; productId: number; regionId: number } + type Region = { id: number; name: string } + + const products = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-materialize-products`, + getKey: (p) => p.id, + initialData: [ + { id: 1, title: `T-Shirt` }, + { id: 2, title: `Hoodie` }, + ], + }), + ) + const priceRanges = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-materialize-price-ranges`, + getKey: (r) => r.id, + initialData: [ + { id: 1, productId: 1, regionId: 1 }, + { id: 2, productId: 1, regionId: 2 }, + { id: 3, productId: 2, regionId: 1 }, + ], + }), + ) + const regions = createCollection( + localOnlyCollectionOptions({ + id: `shared-corr-materialize-regions`, + getKey: (r) => r.id, + initialData: [ + { id: 1, name: `Europe` }, + { id: 2, name: `North America` }, + ], + }), + ) + + await Promise.all([ + products.preload(), + priceRanges.preload(), + regions.preload(), + ]) + + const collection = createLiveQueryCollection({ + id: `shared-corr-materialize-live`, + query: (q) => + q.from({ p: products }).select(({ p }) => ({ + id: p.id, + title: p.title, + priceRanges: materialize( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: materialize( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), + })), + }) + await collection.preload() + + expect(toTree(collection)).toEqual([ + { + id: 1, + title: `T-Shirt`, + priceRanges: [ + { id: 1, regionId: 1, region: [{ id: 1, name: `Europe` }] }, + { + id: 2, + regionId: 2, + region: [{ id: 2, name: `North America` }], + }, + ], + }, + { + id: 2, + title: `Hoodie`, + priceRanges: [ + { id: 3, regionId: 1, region: [{ id: 1, name: `Europe` }] }, + ], + }, + ]) + + // Post-load: insert a price range under Hoodie that references regionId 2, + // a correlation key already materialized for T-Shirt at load. This drives + // the late-arrival snapshot re-emit path through materialize() — the new + // sibling group must be seeded with the already-drained North America row + // without disturbing T-Shirt's existing nested rows. + priceRanges.insert({ id: 4, productId: 2, regionId: 2 }) + await new Promise((r) => setTimeout(r, 50)) + + const tree = toTree(collection) + const tshirt = tree.find((p: any) => p.title === `T-Shirt`) + const hoodie = tree.find((p: any) => p.title === `Hoodie`) + expect(tshirt.priceRanges.find((pr: any) => pr.id === 2).region).toEqual([ + { id: 2, name: `North America` }, + ]) + expect(hoodie.priceRanges.find((pr: any) => pr.id === 4).region).toEqual([ + { id: 2, name: `North America` }, + ]) + }) }) describe(`many sibling toArray includes with chained derived collections`, () => { From 7ce457ee826904a4017cab57305a53cb78a2d9d9 Mon Sep 17 00:00:00 2001 From: "Jaime R." <38530589+Jaime02@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:37:13 +0200 Subject: [PATCH 25/58] Fix unused vars (#1627) --- packages/db/tests/query/select.test-d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/db/tests/query/select.test-d.ts b/packages/db/tests/query/select.test-d.ts index c40b3607df..f4ec842922 100644 --- a/packages/db/tests/query/select.test-d.ts +++ b/packages/db/tests/query/select.test-d.ts @@ -215,15 +215,15 @@ describe(`select types`, () => { const col = createLiveQueryCollection((q) => q.from({ u: spreadUsers }).select(({ u }) => { - const { nickname, ...withoutNickname } = u + const { nickname: _nickname, ...withoutNickname } = u return { trimmed: withoutNickname } }), ) - const result = col.toArray[0]! + const _result = col.toArray[0]! // `nickname` was destructured out, so the projected object must // not reintroduce the key. - type HasNickname = `nickname` extends keyof typeof result.trimmed + type HasNickname = `nickname` extends keyof typeof _result.trimmed ? true : false expectTypeOf().toEqualTypeOf() From 2b3d5e50094bbfa0cd72a1b29c4cba2d333ea290 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 11:18:51 -0600 Subject: [PATCH 26/58] ci: Version Packages (#1628) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fix-ref-union-collapse-b.md | 5 -- .../nested-toarray-shared-buffer-overlap.md | 9 --- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 7 ++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 12 ++++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 60 files changed, 268 insertions(+), 106 deletions(-) delete mode 100644 .changeset/fix-ref-union-collapse-b.md delete mode 100644 .changeset/nested-toarray-shared-buffer-overlap.md diff --git a/.changeset/fix-ref-union-collapse-b.md b/.changeset/fix-ref-union-collapse-b.md deleted file mode 100644 index 05e42450a2..0000000000 --- a/.changeset/fix-ref-union-collapse-b.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Fix `.select()` collapsing discriminated-union fields to the intersection of common keys (#1511). `Ref` now distributes over `T` so `keyof (A | B | C)` no longer reduces the union to its common keys, and `ExtractRef` now distinguishes a real branded `Ref` (where the underlying user type `U` can be returned directly) from a spread-produced inline object (which still needs to be projected through `ResultTypeFromSelect`). This preserves discriminated unions both when the field is selected at the top level and when the field is nested inside another selected object. The real-`Ref` detection uses a strict structural equivalence against the canonical `Ref` shape, so spread-derived objects that keep the same keys but change a field's type (e.g. `{ ...u, code: u.slug }`) or drop an optional key (e.g. `const { nickname, ...rest } = u`) are projected through `ResultTypeFromSelect` instead of being collapsed back to `U`. diff --git a/.changeset/nested-toarray-shared-buffer-overlap.md b/.changeset/nested-toarray-shared-buffer-overlap.md deleted file mode 100644 index 7e0cc5399b..0000000000 --- a/.changeset/nested-toarray-shared-buffer-overlap.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@tanstack/db': patch ---- - -fix(db): keep deeply nested includes in sync when sibling groups share nested correlation keys - -Deeply nested includes could drop or stop updating nested rows when sibling parent groups shared the same nested correlation key, especially when one sibling group was inserted after the initial load. Shared nested pipeline buffers were being drained through route state that was scoped too narrowly, so one branch could consume a buffered update before other branches that referenced the same nested row received it. - -Nested route state is now shared at the same scope as the nested buffer and routes updates to every concrete destination branch before clearing the buffer. Snapshot replay still seeds late-arriving sibling groups with already-materialized rows, and recursive pending-change detection ensures deeper routed updates are flushed back up through the result tree. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index 51cb7ea35f..f5a3d87350 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.72", - "@tanstack/db": "^0.6.12", + "@tanstack/angular-db": "^0.1.73", + "@tanstack/db": "^0.6.13", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index 8d5cbb0b78..ff03515d2b 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.16", - "@tanstack/node-db-sqlite-persistence": "^0.2.4", - "@tanstack/offline-transactions": "^1.0.37", - "@tanstack/query-db-collection": "^1.0.44", - "@tanstack/react-db": "^0.1.90", + "@tanstack/electron-db-sqlite-persistence": "^0.1.17", + "@tanstack/node-db-sqlite-persistence": "^0.2.5", + "@tanstack/offline-transactions": "^1.0.38", + "@tanstack/query-db-collection": "^1.0.45", + "@tanstack/react-db": "^0.1.91", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 754823adf7..dbe8258e87 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.12", - "@tanstack/offline-transactions": "^1.0.37", - "@tanstack/query-db-collection": "^1.0.44", - "@tanstack/react-db": "^0.1.90", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.4", + "@tanstack/db": "^0.6.13", + "@tanstack/offline-transactions": "^1.0.38", + "@tanstack/query-db-collection": "^1.0.45", + "@tanstack/react-db": "^0.1.91", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.5", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 4ded14d59a..eef8653abf 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.12", - "@tanstack/electric-db-collection": "^0.3.10", - "@tanstack/offline-transactions": "^1.0.37", - "@tanstack/react-db": "^0.1.90", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.4", + "@tanstack/db": "^0.6.13", + "@tanstack/electric-db-collection": "^0.3.11", + "@tanstack/offline-transactions": "^1.0.38", + "@tanstack/react-db": "^0.1.91", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.5", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index 8d9e0174f6..e82ad6974b 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.4", - "@tanstack/db": "^0.6.12", - "@tanstack/offline-transactions": "^1.0.37", - "@tanstack/query-db-collection": "^1.0.44", - "@tanstack/react-db": "^0.1.90", + "@tanstack/browser-db-sqlite-persistence": "^0.2.5", + "@tanstack/db": "^0.6.13", + "@tanstack/offline-transactions": "^1.0.38", + "@tanstack/query-db-collection": "^1.0.45", + "@tanstack/react-db": "^0.1.91", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 0b655db848..19bcf28c54 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.12", - "@tanstack/react-db": "^0.1.90", + "@tanstack/db": "^0.6.13", + "@tanstack/react-db": "^0.1.91", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 5b86d9397f..38cb77dcfc 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.44", - "@tanstack/react-db": "^0.1.90", + "@tanstack/query-db-collection": "^1.0.45", + "@tanstack/react-db": "^0.1.91", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index fa35da8b2a..666f022d65 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.10", + "@tanstack/electric-db-collection": "^0.3.11", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.44", - "@tanstack/react-db": "^0.1.90", + "@tanstack/query-db-collection": "^1.0.45", + "@tanstack/react-db": "^0.1.91", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.90", + "@tanstack/trailbase-db-collection": "^0.1.91", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index d1c630256e..649e07ac1a 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.10", + "@tanstack/electric-db-collection": "^0.3.11", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.44", - "@tanstack/solid-db": "^0.2.26", + "@tanstack/query-db-collection": "^1.0.45", + "@tanstack/solid-db": "^0.2.27", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.90", + "@tanstack/trailbase-db-collection": "^0.1.91", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index a9311a8bbb..730c62caf2 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.73 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.1.72 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 0732ceb0e7..d1bc7330ec 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.72", + "version": "0.1.73", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index e19f17b756..169f4d6d3e 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.5 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.2.4 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index c7648d8eaa..07245c30d3 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.4", + "version": "0.2.5", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 9d9d871066..3ce05646bd 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.5 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.2.4 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index d9bbdac5fd..71a1fc99d6 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.17 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + - @tanstack/capacitor-db-sqlite-persistence@0.2.5 + ## 0.0.16 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 5efdbeb19d..1757abaed5 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.16", + "version": "0.0.17", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index ce498970bc..49a94b8a6f 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.4", + "version": "0.2.5", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index 248da3ddb1..6837d87831 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.5 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.2.4 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 8f1b4704c1..aa560be7fc 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.4", + "version": "0.2.5", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index 493036319c..aadac580e7 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.5 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.2.4 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index e6cad92c3f..4328a7cf8e 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.4", + "version": "0.2.5", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index 24e61d7d88..0b400c897d 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,17 @@ # @tanstack/db +## 0.6.13 + +### Patch Changes + +- Fix `.select()` collapsing discriminated-union fields to the intersection of common keys (#1511). `Ref` now distributes over `T` so `keyof (A | B | C)` no longer reduces the union to its common keys, and `ExtractRef` now distinguishes a real branded `Ref` (where the underlying user type `U` can be returned directly) from a spread-produced inline object (which still needs to be projected through `ResultTypeFromSelect`). This preserves discriminated unions both when the field is selected at the top level and when the field is nested inside another selected object. The real-`Ref` detection uses a strict structural equivalence against the canonical `Ref` shape, so spread-derived objects that keep the same keys but change a field's type (e.g. `{ ...u, code: u.slug }`) or drop an optional key (e.g. `const { nickname, ...rest } = u`) are projected through `ResultTypeFromSelect` instead of being collapsed back to `U`. ([#1597](https://github.com/TanStack/db/pull/1597)) + +- fix(db): keep deeply nested includes in sync when sibling groups share nested correlation keys ([#1607](https://github.com/TanStack/db/pull/1607)) + + Deeply nested includes could drop or stop updating nested rows when sibling parent groups shared the same nested correlation key, especially when one sibling group was inserted after the initial load. Shared nested pipeline buffers were being drained through route state that was scoped too narrowly, so one branch could consume a buffered update before other branches that referenced the same nested row received it. + + Nested route state is now shared at the same scope as the nested buffer and routes updates to every concrete destination branch before clearing the buffer. Snapshot replay still seeds late-arriving sibling groups with already-materialized rows, and recursive pending-change detection ensures deeper routed updates are flushed back up through the result tree. + ## 0.6.12 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 38a0899fe1..1076c93429 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.12", + "version": "0.6.13", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 123cd00ff6..96d5a3a553 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.11 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.3.10 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index d347213726..940f54646f 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.10", + "version": "0.3.11", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 4a338e854e..2806980126 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.17 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.1.16 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 3ebf475002..81332339f9 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.16", + "version": "0.1.17", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index 238aa5e684..5cbb03595d 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.5 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.2.4 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index 0afd282d65..a96e98481c 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.17 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + - @tanstack/expo-db-sqlite-persistence@0.2.5 + ## 0.0.16 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 1d2512d18a..80e11b573d 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.16", + "version": "0.0.17", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 8e073ce4d9..78d050e600 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.4", + "version": "0.2.5", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index d53e303362..c3a4f5e701 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.5 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.2.4 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index 907d6a91d2..2dbcd64c2f 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.4", + "version": "0.2.5", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index 46b8214db0..e20402137c 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.38 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 1.0.37 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index ae41b865c8..fe5435a5a9 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.37", + "version": "1.0.38", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index eabe915067..cd77211b32 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.51 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.1.50 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index e3fc9bf017..2bf539fd2f 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.50", + "version": "0.1.51", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index e13d824ce0..04c39dd80d 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.45 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 1.0.44 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index e4ff257275..e42a9b2460 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.44", + "version": "1.0.45", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index 5f1df1b021..eb8d6c72ae 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.91 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.1.90 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 35f2976db2..fc96bd232f 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.90", + "version": "0.1.91", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index ab2a1e5ab7..84d30f9296 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.5 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.2.4 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index a2748d89d1..393103866e 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.4", + "version": "0.2.5", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 3838244c6e..f06665d250 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.79 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.1.78 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index 036b383abd..81c61496e2 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.78", + "version": "0.1.79", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index dc19544744..edeb6cfa6a 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.27 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.2.26 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index 4fb86f09aa..700bf867b4 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.26", + "version": "0.2.27", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 47d69ca0aa..43d005abd9 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.90 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.1.89 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index 5a8d7c2fd8..47bd338128 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.89", + "version": "0.1.90", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index b0c2f50cde..c18b1a96b9 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.5 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.5 + ## 0.2.4 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 0c2cd6af1b..9bf878ffb7 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.17 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + - @tanstack/tauri-db-sqlite-persistence@0.2.5 + ## 0.0.16 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 5dc3b0f7a7..28d148e357 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.16", + "version": "0.0.17", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index 769840a599..dc635feabf 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.4", + "version": "0.2.5", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index 06abe923a5..aa706dfe2f 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.91 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.1.90 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 94a49c1367..35a688d5c7 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.90", + "version": "0.1.91", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 465ebed48c..5cba73a4fd 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.124 + +### Patch Changes + +- Updated dependencies [[`99e9afe`](https://github.com/TanStack/db/commit/99e9afed46ab4083d66609a3e37ee44103c2177f), [`816b667`](https://github.com/TanStack/db/commit/816b6671c2cc9806715f6e6ed4410b3f4efb5afb)]: + - @tanstack/db@0.6.13 + ## 0.0.123 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index b90cba7037..c198d6a663 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.123", + "version": "0.0.124", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfbc4a8ef9..87d84c8819 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.72 + specifier: ^0.1.73 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.12 + specifier: ^0.6.13 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.16 + specifier: ^0.1.17 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.4 + specifier: ^0.2.5 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.37 + specifier: ^1.0.38 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.44 + specifier: ^1.0.45 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.12 + specifier: ^0.6.13 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.37 + specifier: ^1.0.38 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.44 + specifier: ^1.0.45 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.4 + specifier: ^0.2.5 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.12 + specifier: ^0.6.13 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.10 + specifier: ^0.3.11 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.37 + specifier: ^1.0.38 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.4 + specifier: ^0.2.5 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.4 + specifier: ^0.2.5 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.12 + specifier: ^0.6.13 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.37 + specifier: ^1.0.38 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.44 + specifier: ^1.0.45 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.12 + specifier: ^0.6.13 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.44 + specifier: ^1.0.45 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.10 + specifier: ^0.3.11 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.44 + specifier: ^1.0.45 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.10 + specifier: ^0.3.11 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.44 + specifier: ^1.0.45 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.26 + specifier: ^0.2.27 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.90 + specifier: ^0.1.91 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From f7da77660b16cbfe30817fb5c938267d696c8d1c Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 1 Jul 2026 09:35:25 +0200 Subject: [PATCH 27/58] fix(sqlite-persistence): preserve query owner metadata on insert (closes #1618) (#1626) * test(query-db-collection): cover persisted row cleanup after reload A row inserted while the collection is mounted should be persisted together with its query owner metadata, so that after a reload the row is removed from both the live collection and the persisted store once the query no longer returns it. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: queued metadata reset on insert * changeset * test(query-db-collection): assert persisted hydration before cleanup --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: Tom Bryden --- .changeset/small-tables-listen.md | 5 + .../src/persisted.ts | 18 ++- .../tests/persisted.test.ts | 72 ++++++++++++ .../query-db-collection/tests/query.test.ts | 107 ++++++++++++++++++ 4 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 .changeset/small-tables-listen.md diff --git a/.changeset/small-tables-listen.md b/.changeset/small-tables-listen.md new file mode 100644 index 0000000000..fbb62047b7 --- /dev/null +++ b/.changeset/small-tables-listen.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db-sqlite-persistence-core': patch +--- + +Fixed bug where internal metadata would get reset on insert causing stale items to remain diff --git a/packages/db-sqlite-persistence-core/src/persisted.ts b/packages/db-sqlite-persistence-core/src/persisted.ts index 12cf8319c3..ca60a050ab 100644 --- a/packages/db-sqlite-persistence-core/src/persisted.ts +++ b/packages/db-sqlite-persistence-core/src/persisted.ts @@ -2290,9 +2290,21 @@ function createWrappedSyncConfig< message.type === `insert` && normalization.operation.metadata === undefined ) { - openTransaction.rowMetadataWrites.set(normalization.operation.key, { - type: `delete`, - }) + // Reset stale metadata for a fresh insert, but don't clobber an + // explicit metadata write already queued for this key in the same + // transaction (e.g. query reconcile stamps owners, then inserts). + if ( + !openTransaction.rowMetadataWrites.has( + normalization.operation.key, + ) + ) { + openTransaction.rowMetadataWrites.set( + normalization.operation.key, + { + type: `delete`, + }, + ) + } } else if (normalization.operation.metadata !== undefined) { openTransaction.rowMetadataWrites.set(normalization.operation.key, { type: `set`, diff --git a/packages/db-sqlite-persistence-core/tests/persisted.test.ts b/packages/db-sqlite-persistence-core/tests/persisted.test.ts index 57c11d8442..42bbcf5633 100644 --- a/packages/db-sqlite-persistence-core/tests/persisted.test.ts +++ b/packages/db-sqlite-persistence-core/tests/persisted.test.ts @@ -816,6 +816,78 @@ describe(`persistedCollectionOptions`, () => { ) }) + it(`preserves row metadata set before a metadata-less insert in the same sync transaction`, async () => { + const adapter = createRecordingAdapter() + const ownership = { queryCollection: { owners: [`gc:q1`] } } + const sync: SyncConfig = { + sync: ({ begin, write, commit, markReady, metadata }) => { + begin() + metadata?.row.set(`remote-1`, ownership) + write({ + type: `insert`, + value: { + id: `remote-1`, + title: `From remote`, + }, + }) + commit() + markReady() + }, + } + + const collection = createCollection( + persistedCollectionOptions({ + id: `sync-present`, + getKey: (item: Todo) => item.id, + sync, + persistence: { + adapter, + }, + }), + ) + + await collection.stateWhenReady() + await flushAsyncWork() + + expect(adapter.rowMetadata.get(`remote-1`)).toEqual(ownership) + expect(collection._state.syncedMetadata.get(`remote-1`)).toEqual(ownership) + }) + + it(`resets stale row metadata for a metadata-less insert with no queued metadata`, async () => { + const adapter = createRecordingAdapter() + adapter.rowMetadata.set(`remote-1`, { stale: true }) + const sync: SyncConfig = { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ + type: `insert`, + value: { + id: `remote-1`, + title: `From remote`, + }, + }) + commit() + markReady() + }, + } + + const collection = createCollection( + persistedCollectionOptions({ + id: `sync-present`, + getKey: (item: Todo) => item.id, + sync, + persistence: { + adapter, + }, + }), + ) + + await collection.stateWhenReady() + await flushAsyncWork() + + expect(adapter.rowMetadata.has(`remote-1`)).toBe(false) + }) + it(`uses a stable generated collection id in sync-present mode when id is omitted`, async () => { const adapter = createRecordingAdapter() const options = persistedCollectionOptions({ diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index b9ccb8f550..db73648c19 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -4960,6 +4960,113 @@ describe(`QueryCollection`, () => { ).toBe(false) }) + it(`should clean up an inserted row dropped by the query after a reload`, async () => { + // A row inserted while the collection is mounted is persisted. After the + // app reloads, if the query no longer returns that row, it must be removed + // from both the live collection and the persisted store. + const queryKey = [`reload-insert-cleanup`] + const makeQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { staleTime: 0, gcTime: 5 * 60 * 1000, retry: false }, + }, + }) + + // The shared on-device store, surviving across the two sessions. + const adapter = createPersistedQueryAdapter({}) + + // ---- First session: insert an item that the server then returns ---- + let serverRows: Array = [] + const firstQueryClient = makeQueryClient() + const collection1 = createCollection( + persistedCollectionOptions({ + ...(queryCollectionOptions({ + id: `reload-insert-cleanup`, + queryClient: firstQueryClient, + queryKey, + queryFn: async () => serverRows, + getKey: (item: CategorisedItem): string => item.id, + syncMode: `eager`, + startSync: true, + onInsert: async ({ transaction }) => { + // The mutation reaches the server: the item now appears in the + // API response for subsequent fetches. + for (const mutation of transaction.mutations) { + serverRows = [...serverRows, mutation.modified] + } + }, + }) as any), + persistence: { adapter }, + }) as any, + ) + + await collection1.stateWhenReady() + await flushPromises() + + await collection1.insert({ id: `1`, name: `Buy milk`, category: `A` }) + // The query refetches and sees the now-synced row. + await firstQueryClient.invalidateQueries({ queryKey }) + await flushPromises() + await flushPromises() + + expect(adapter.rows.has(`1`)).toBe(true) + + // ---- App closes; the row is removed on the server out of band ---- + serverRows = [] + + // ---- Second session: reload from the persisted store ---- + const secondQueryClient = makeQueryClient() + const adapter2 = createPersistedQueryAdapter({ + rows: adapter.rows, + rowMetadata: adapter.rowMetadata, + collectionMetadata: adapter.collectionMetadata, + }) + let releaseSecondFetch!: () => void + const secondFetchReleased = new Promise((resolve) => { + releaseSecondFetch = resolve + }) + const collection2 = createCollection( + persistedCollectionOptions({ + ...(queryCollectionOptions({ + id: `reload-insert-cleanup`, + queryClient: secondQueryClient, + queryKey, + queryFn: async () => { + await secondFetchReleased + return serverRows + }, + getKey: (item: CategorisedItem): string => item.id, + syncMode: `eager`, + startSync: true, + }) as any), + persistence: { adapter: adapter2 }, + }) as any, + ) + + await vi.waitFor(() => { + expect(collection2.has(`1`)).toBe(true) + expect(adapter2.rows.has(`1`)).toBe(true) + }) + + const liveQuery = createLiveQueryCollection({ + query: (q) => q.from({ item: collection2 }), + }) + releaseSecondFetch() + await liveQuery.preload() + // The query responds on launch (after persisted rows have hydrated). + await flushPromises() + await flushPromises() + + await vi.waitFor(() => { + expect(collection2.has(`1`)).toBe(false) + expect(adapter2.rows.has(`1`)).toBe(false) + expect(liveQuery.size).toBe(0) + }) + + firstQueryClient.clear() + secondQueryClient.clear() + }) + it(`should expire retained ttl placeholders while the app stays open`, async () => { vi.useFakeTimers() try { From 397e12a1224ad563e20a331eebcbe904cd4af948 Mon Sep 17 00:00:00 2001 From: Sam Willis Date: Thu, 2 Jul 2026 21:47:25 +0800 Subject: [PATCH 28/58] fix(db): avoid full row origin snapshots (#1640) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: avoid full row origin snapshots * chore: add row origin snapshot changeset * perf(db): make Transaction.applyMutations O(n) instead of O(n²) applyMutations did a findIndex scan over the existing mutations array for every incoming mutation, making bulk operations quadratic: a single insert() of 50k rows spent most of its time in this scan. Merge through a globalKey-keyed Map instead, preserving insertion order and rebuilding the array in place to keep its identity for external holders. Co-Authored-By: Claude Fable 5 * chore: update perf changeset --------- Co-authored-by: Kevin De Porre Co-authored-by: Claude Fable 5 --- .changeset/quiet-row-origins.md | 5 +++++ packages/db/src/collection/state.ts | 33 ++++++++++++++++++++++++----- packages/db/src/transactions.ts | 28 +++++++++++++++++------- 3 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 .changeset/quiet-row-origins.md diff --git a/.changeset/quiet-row-origins.md b/.changeset/quiet-row-origins.md new file mode 100644 index 0000000000..06b28d6833 --- /dev/null +++ b/.changeset/quiet-row-origins.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Avoid full row origin snapshots during incremental collection updates and make bulk mutation merging linear. diff --git a/packages/db/src/collection/state.ts b/packages/db/src/collection/state.ts index 9cbdebb234..5f4449c3b0 100644 --- a/packages/db/src/collection/state.ts +++ b/packages/db/src/collection/state.ts @@ -226,6 +226,21 @@ export class CollectionStateManager< }) } + private snapshotRowOriginsForKeys( + keys: Iterable, + ): Map { + const rowOrigins = new Map() + + for (const key of keys) { + const origin = this.rowOrigins.get(key) + if (origin !== undefined) { + rowOrigins.set(key, origin) + } + } + + return rowOrigins + } + private enrichWithVirtualPropsSnapshot( row: TOutput, virtualProps: VirtualRowProps, @@ -476,7 +491,7 @@ export class CollectionStateManager< const previousState = new Map(this.optimisticUpserts) const previousDeletes = new Set(this.optimisticDeletes) - const previousRowOrigins = new Map(this.rowOrigins) + const previousRowOrigins = this.rowOrigins // Update pending optimistic state for completed/failed transactions for (const transaction of this.transactions.values()) { @@ -857,10 +872,6 @@ export class CollectionStateManager< // Set flag to prevent redundant optimistic state recalculations this.isCommittingSyncTransactions = true - const previousRowOrigins = new Map(this.rowOrigins) - const previousOptimisticUpserts = new Map(this.optimisticUpserts) - const previousOptimisticDeletes = new Set(this.optimisticDeletes) - // Get the optimistic snapshot from the truncate transaction (captured when truncate() was called) const truncateOptimisticSnapshot = hasTruncateSync ? committedSyncedTransactions.find((t) => t.truncate) @@ -880,6 +891,18 @@ export class CollectionStateManager< } } + const virtualSnapshotKeys = new Set(changedKeys) + for (const key of this.pendingOptimisticDirectUpserts) { + virtualSnapshotKeys.add(key) + } + for (const key of this.pendingOptimisticDirectDeletes) { + virtualSnapshotKeys.add(key) + } + const previousRowOrigins = + this.snapshotRowOriginsForKeys(virtualSnapshotKeys) + const previousOptimisticUpserts = new Map(this.optimisticUpserts) + const previousOptimisticDeletes = new Set(this.optimisticDeletes) + // Use pre-captured state if available (from optimistic scenarios), // otherwise capture current state (for pure sync scenarios) let currentVisibleState = this.preSyncVisibleState diff --git a/packages/db/src/transactions.ts b/packages/db/src/transactions.ts index fe2f61c0fd..84cbbcfe58 100644 --- a/packages/db/src/transactions.ts +++ b/packages/db/src/transactions.ts @@ -334,27 +334,39 @@ class Transaction> { * @param mutations - Array of new mutations to apply */ applyMutations(mutations: Array>): void { + // Merge via a globalKey-keyed map rather than a findIndex scan per + // mutation, which is O(n²) for bulk operations (e.g. inserting many rows + // in one call). Map preserves insertion order, matching the previous + // replace-in-place / remove / append semantics. + const merged = new Map>() + for (const mutation of this.mutations) { + merged.set(mutation.globalKey, mutation) + } + for (const newMutation of mutations) { - const existingIndex = this.mutations.findIndex( - (m) => m.globalKey === newMutation.globalKey, - ) + const existingMutation = merged.get(newMutation.globalKey) - if (existingIndex >= 0) { - const existingMutation = this.mutations[existingIndex]! + if (existingMutation) { const mergeResult = mergePendingMutations(existingMutation, newMutation) if (mergeResult === null) { // Remove the mutation (e.g., delete after insert cancels both) - this.mutations.splice(existingIndex, 1) + merged.delete(newMutation.globalKey) } else { // Replace with merged mutation - this.mutations[existingIndex] = mergeResult + merged.set(newMutation.globalKey, mergeResult) } } else { // Insert new mutation - this.mutations.push(newMutation) + merged.set(newMutation.globalKey, newMutation) } } + + // Rebuild in place to preserve the array's identity for external holders + this.mutations.length = 0 + for (const mutation of merged.values()) { + this.mutations.push(mutation) + } } /** From 95e25bd1ec90187ac18bbea19522780936d48025 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:57:52 +0100 Subject: [PATCH 29/58] ci: Version Packages (#1632) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/quiet-row-origins.md | 5 -- .changeset/small-tables-listen.md | 5 -- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 9 +++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 6 ++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 60 files changed, 264 insertions(+), 102 deletions(-) delete mode 100644 .changeset/quiet-row-origins.md delete mode 100644 .changeset/small-tables-listen.md diff --git a/.changeset/quiet-row-origins.md b/.changeset/quiet-row-origins.md deleted file mode 100644 index 06b28d6833..0000000000 --- a/.changeset/quiet-row-origins.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Avoid full row origin snapshots during incremental collection updates and make bulk mutation merging linear. diff --git a/.changeset/small-tables-listen.md b/.changeset/small-tables-listen.md deleted file mode 100644 index fbb62047b7..0000000000 --- a/.changeset/small-tables-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': patch ---- - -Fixed bug where internal metadata would get reset on insert causing stale items to remain diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index f5a3d87350..36f8d79a61 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.73", - "@tanstack/db": "^0.6.13", + "@tanstack/angular-db": "^0.1.74", + "@tanstack/db": "^0.6.14", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index ff03515d2b..b7fb2bd738 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.17", - "@tanstack/node-db-sqlite-persistence": "^0.2.5", - "@tanstack/offline-transactions": "^1.0.38", - "@tanstack/query-db-collection": "^1.0.45", - "@tanstack/react-db": "^0.1.91", + "@tanstack/electron-db-sqlite-persistence": "^0.1.18", + "@tanstack/node-db-sqlite-persistence": "^0.2.6", + "@tanstack/offline-transactions": "^1.0.39", + "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/react-db": "^0.1.92", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index dbe8258e87..3e5583bb85 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.13", - "@tanstack/offline-transactions": "^1.0.38", - "@tanstack/query-db-collection": "^1.0.45", - "@tanstack/react-db": "^0.1.91", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.5", + "@tanstack/db": "^0.6.14", + "@tanstack/offline-transactions": "^1.0.39", + "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/react-db": "^0.1.92", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.6", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index eef8653abf..513cd4d333 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.13", - "@tanstack/electric-db-collection": "^0.3.11", - "@tanstack/offline-transactions": "^1.0.38", - "@tanstack/react-db": "^0.1.91", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.5", + "@tanstack/db": "^0.6.14", + "@tanstack/electric-db-collection": "^0.3.12", + "@tanstack/offline-transactions": "^1.0.39", + "@tanstack/react-db": "^0.1.92", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.6", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index e82ad6974b..9a105cd1c7 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.5", - "@tanstack/db": "^0.6.13", - "@tanstack/offline-transactions": "^1.0.38", - "@tanstack/query-db-collection": "^1.0.45", - "@tanstack/react-db": "^0.1.91", + "@tanstack/browser-db-sqlite-persistence": "^0.2.6", + "@tanstack/db": "^0.6.14", + "@tanstack/offline-transactions": "^1.0.39", + "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/react-db": "^0.1.92", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 19bcf28c54..2485cc9206 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.13", - "@tanstack/react-db": "^0.1.91", + "@tanstack/db": "^0.6.14", + "@tanstack/react-db": "^0.1.92", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 38cb77dcfc..301f41c85d 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.45", - "@tanstack/react-db": "^0.1.91", + "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/react-db": "^0.1.92", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index 666f022d65..f664674aa6 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.11", + "@tanstack/electric-db-collection": "^0.3.12", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.45", - "@tanstack/react-db": "^0.1.91", + "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/react-db": "^0.1.92", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.91", + "@tanstack/trailbase-db-collection": "^0.1.92", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index 649e07ac1a..cd4bcef55a 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.11", + "@tanstack/electric-db-collection": "^0.3.12", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.45", - "@tanstack/solid-db": "^0.2.27", + "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/solid-db": "^0.2.28", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.91", + "@tanstack/trailbase-db-collection": "^0.1.92", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 730c62caf2..07273f51e4 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.74 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.1.73 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index d1bc7330ec..33ef8684b6 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.73", + "version": "0.1.74", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index 169f4d6d3e..e7ae0b48f9 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.6 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.2.5 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 07245c30d3..2a984e17c1 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.5", + "version": "0.2.6", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 3ce05646bd..705d509c9e 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.6 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.2.5 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index 71a1fc99d6..04ca78e471 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.18 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + - @tanstack/capacitor-db-sqlite-persistence@0.2.6 + ## 0.0.17 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 1757abaed5..ce75ad42ad 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.17", + "version": "0.0.18", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 49a94b8a6f..08af68894f 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.5", + "version": "0.2.6", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index 6837d87831..bfdba6a502 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.6 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.2.5 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index aa560be7fc..a70de7a1c4 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.5", + "version": "0.2.6", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index aadac580e7..f7bb4e1142 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.6 + +### Patch Changes + +- Fixed bug where internal metadata would get reset on insert causing stale items to remain ([#1626](https://github.com/TanStack/db/pull/1626)) + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.2.5 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 4328a7cf8e..3d2762a41c 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.5", + "version": "0.2.6", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index 0b400c897d..a6e264f54e 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,11 @@ # @tanstack/db +## 0.6.14 + +### Patch Changes + +- Avoid full row origin snapshots during incremental collection updates and make bulk mutation merging linear. ([#1640](https://github.com/TanStack/db/pull/1640)) + ## 0.6.13 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 1076c93429..3964473ad3 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.13", + "version": "0.6.14", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 96d5a3a553..573abc3c0c 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.12 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.3.11 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index 940f54646f..6a0e2677d2 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.11", + "version": "0.3.12", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 2806980126..d727a23d17 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.18 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.1.17 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 81332339f9..586e2b8a41 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.17", + "version": "0.1.18", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index 5cbb03595d..5af1edf510 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.6 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.2.5 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index a96e98481c..b40a888144 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.18 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + - @tanstack/expo-db-sqlite-persistence@0.2.6 + ## 0.0.17 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 80e11b573d..e1b0e245fb 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.17", + "version": "0.0.18", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 78d050e600..324dff95ac 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.5", + "version": "0.2.6", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index c3a4f5e701..5145428dbb 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.6 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.2.5 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index 2dbcd64c2f..6df0e4a2b8 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.5", + "version": "0.2.6", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index e20402137c..c1c47150f3 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.39 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 1.0.38 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index fe5435a5a9..124b787c08 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.38", + "version": "1.0.39", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index cd77211b32..02d39c72ae 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.52 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.1.51 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 2bf539fd2f..fd3978c83e 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.51", + "version": "0.1.52", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index 04c39dd80d..478e3d607e 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.46 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 1.0.45 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index e42a9b2460..ec17681dac 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.45", + "version": "1.0.46", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index eb8d6c72ae..a7f09f0039 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.92 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.1.91 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index fc96bd232f..175e5a7099 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.91", + "version": "0.1.92", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 84d30f9296..6f2c4f0098 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.6 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.2.5 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index 393103866e..cd46124d54 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.5", + "version": "0.2.6", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index f06665d250..88a2b5bb30 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.80 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.1.79 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index 81c61496e2..fa341f2fbe 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.79", + "version": "0.1.80", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index edeb6cfa6a..9f2d588391 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.28 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.2.27 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index 700bf867b4..b025bbee6e 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.27", + "version": "0.2.28", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 43d005abd9..7505d18cb2 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.91 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.1.90 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index 47bd338128..e340f539a3 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.90", + "version": "0.1.91", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index c18b1a96b9..d01531deb4 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.6 + +### Patch Changes + +- Updated dependencies [[`f7da776`](https://github.com/TanStack/db/commit/f7da77660b16cbfe30817fb5c938267d696c8d1c)]: + - @tanstack/db-sqlite-persistence-core@0.2.6 + ## 0.2.5 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 9bf878ffb7..269a6acf17 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.18 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + - @tanstack/tauri-db-sqlite-persistence@0.2.6 + ## 0.0.17 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 28d148e357..34d33ecd72 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.17", + "version": "0.0.18", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index dc635feabf..9111062bfb 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.5", + "version": "0.2.6", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index aa706dfe2f..5616e1757f 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.92 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.1.91 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 35a688d5c7..1a2dc200c4 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.91", + "version": "0.1.92", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 5cba73a4fd..00024f6395 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.125 + +### Patch Changes + +- Updated dependencies [[`397e12a`](https://github.com/TanStack/db/commit/397e12a1224ad563e20a331eebcbe904cd4af948)]: + - @tanstack/db@0.6.14 + ## 0.0.124 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index c198d6a663..ff4f7771dd 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.124", + "version": "0.0.125", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87d84c8819..5a80819102 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.73 + specifier: ^0.1.74 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.13 + specifier: ^0.6.14 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.17 + specifier: ^0.1.18 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.5 + specifier: ^0.2.6 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.38 + specifier: ^1.0.39 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.45 + specifier: ^1.0.46 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.13 + specifier: ^0.6.14 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.38 + specifier: ^1.0.39 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.45 + specifier: ^1.0.46 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.5 + specifier: ^0.2.6 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.13 + specifier: ^0.6.14 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.11 + specifier: ^0.3.12 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.38 + specifier: ^1.0.39 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.5 + specifier: ^0.2.6 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.5 + specifier: ^0.2.6 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.13 + specifier: ^0.6.14 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.38 + specifier: ^1.0.39 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.45 + specifier: ^1.0.46 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.13 + specifier: ^0.6.14 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.45 + specifier: ^1.0.46 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.11 + specifier: ^0.3.12 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.45 + specifier: ^1.0.46 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.11 + specifier: ^0.3.12 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.45 + specifier: ^1.0.46 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.27 + specifier: ^0.2.28 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.91 + specifier: ^0.1.92 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 3ec3e7ae1b2fa5ab9fee3ebaebb6d97c9487d29b Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 7 Jul 2026 12:20:14 +0200 Subject: [PATCH 30/58] test: cross-adapter useLiveQuery conformance suite A shared, framework-agnostic conformance suite for the useLiveQuery adapters: one behavioral spec runs against React, Vue, Svelte, Solid, and Angular through a thin per-adapter test harness. Documents current behavior and guards later refactors. --- packages/angular-db/tests/conformance.test.ts | 227 ++++++ packages/db/tests/conformance/contract.ts | 149 ++++ packages/db/tests/conformance/suite.ts | 694 ++++++++++++++++++ packages/react-db/tests/conformance.test.tsx | 213 ++++++ packages/solid-db/tests/conformance.test.tsx | 226 ++++++ .../tests/conformance.svelte.test.ts | 223 ++++++ packages/vue-db/tests/conformance.test.ts | 219 ++++++ 7 files changed, 1951 insertions(+) create mode 100644 packages/angular-db/tests/conformance.test.ts create mode 100644 packages/db/tests/conformance/contract.ts create mode 100644 packages/db/tests/conformance/suite.ts create mode 100644 packages/react-db/tests/conformance.test.tsx create mode 100644 packages/solid-db/tests/conformance.test.tsx create mode 100644 packages/svelte-db/tests/conformance.svelte.test.ts create mode 100644 packages/vue-db/tests/conformance.test.ts diff --git a/packages/angular-db/tests/conformance.test.ts b/packages/angular-db/tests/conformance.test.ts new file mode 100644 index 0000000000..b60b8fa3d1 --- /dev/null +++ b/packages/angular-db/tests/conformance.test.ts @@ -0,0 +1,227 @@ +/** + * Angular driver for the shared live-query conformance suite. + * + * `injectLiveQuery` needs an injection context, so each mount runs inside a + * child `EnvironmentInjector` created off TestBed's; `unmount` calls + * `injector.destroy()`, firing the `DestroyRef` cleanup. Result signals are read + * after settling. Controllable inputs use Angular's reactive `{ params, query }` + * form driven by a signal. + * + * `knownGaps` is populated empirically from the run below. + */ +import { + EnvironmentInjector, + createEnvironmentInjector, + runInInjectionContext, + signal, +} from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { + coalesce, + count, + createCollection, + createLiveQueryCollection, + createOptimisticAction, + eq, + gt, + sum, +} from '@tanstack/db' +import { + mockSyncCollectionOptions, + mockSyncCollectionOptionsNoInitialState, +} from '../../db/tests/utils' +import { injectLiveQuery } from '../src/index' +import { runSuite } from '../../db/tests/conformance/suite' +import type { + ConformanceResult, + ControllableHandle, + DeferredSourceHandle, + LiveQueryDriver, + LiveQueryHandle, + QueryBuild, + SourceHandle, +} from '../../db/tests/conformance/contract' + +let sourceSeq = 0 + +function writer(collection: any) { + return (type: `insert` | `update` | `delete`, value: T) => { + collection.utils.begin() + collection.utils.write({ type, value }) + collection.utils.commit() + } +} + +function makeSource( + initialData: ReadonlyArray, +): SourceHandle { + const collection = createCollection( + mockSyncCollectionOptions({ + id: `conformance-angular-${sourceSeq++}`, + getKey: (r) => r.id, + initialData: [...initialData], + }), + ) + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + } +} + +function makeDeferredSource< + T extends { id: string }, +>(): DeferredSourceHandle { + const collection = createCollection( + mockSyncCollectionOptionsNoInitialState({ + id: `conformance-angular-${sourceSeq++}`, + getKey: (r) => r.id, + }), + ) + collection.startSyncImmediate() + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + emit: (rows) => { + collection.utils.begin() + rows.forEach((value) => collection.utils.write({ type: `insert`, value })) + collection.utils.commit() + }, + markReady: () => collection.utils.markReady(), + } +} + +function makePrecreated(build: QueryBuild, opts?: { startSync?: boolean }) { + const collection = createLiveQueryCollection({ + query: build as any, + startSync: opts?.startSync ?? true, + }) + return { collection } +} + +function makeErrorSource() { + const collection = createCollection<{ id: string }>({ + id: `conformance-angular-err-${sourceSeq++}`, + getKey: (r) => r.id, + startSync: false, + sync: { + sync: () => { + throw new Error(`conformance: sync failure`) + }, + }, + }) + try { + collection.startSyncImmediate() + } catch { + // expected: engine catches the sync error and sets status to `error` + } + return { collection } +} + +async function settle() { + await new Promise((resolve) => setTimeout(resolve, 0)) + await new Promise((resolve) => setTimeout(resolve, 50)) +} + +function makeHandle(result: any, destroy: () => void): LiveQueryHandle { + return { + current(): ConformanceResult { + return { + data: result.data(), + status: result.status(), + isReady: Boolean(result.isReady()), + isError: Boolean(result.isError()), + // angular-db exposes no `isEnabled`; derive it from status (status-derived). + isEnabled: result.status() !== `disabled`, + } + }, + flush: settle, + async apply(fn: () => void) { + fn() + await settle() + }, + unmount() { + destroy() + }, + } +} + +function inCtx(fn: () => any): { result: any; destroy: () => void } { + const parent = TestBed.inject(EnvironmentInjector) + const injector = createEnvironmentInjector([], parent) + let result: any + runInInjectionContext(injector, () => { + result = fn() + }) + return { result, destroy: () => injector.destroy() } +} + +function mount(build: QueryBuild) { + const { result, destroy } = inCtx(() => injectLiveQuery(build as any)) + return makeHandle(result, destroy) +} + +function mountCollection(collection: any) { + const { result, destroy } = inCtx(() => injectLiveQuery(collection)) + return makeHandle(result, destroy) +} + +function mountConfig(build: QueryBuild) { + const { result, destroy } = inCtx(() => injectLiveQuery({ query: build })) + return makeHandle(result, destroy) +} + +function mountDisabled() { + const { result, destroy } = inCtx(() => injectLiveQuery(() => null)) + return makeHandle(result, destroy) +} + +function mountControllable

( + build: (q: any, param: P) => any, + initial: P, +): ControllableHandle

{ + const param = signal

(initial) + const { result, destroy } = inCtx(() => + injectLiveQuery({ + params: () => ({ value: param() }), + query: ({ params, q }: any) => build(q, params.value), + }), + ) + const handle = makeHandle(result, destroy) + return { + ...handle, + async setParam(next: P) { + param.set(next) + await settle() + }, + } +} + +const angularDriver: LiveQueryDriver = { + name: `angular`, + ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + makeSource, + makeDeferredSource, + makePrecreated, + makeErrorSource, + mount, + mountControllable, + mountCollection, + mountConfig, + mountDisabled, + // Divergence the suite surfaced: angular-db's plain `{ query }` config-object + // path calls createLiveQueryCollection(opts) as-is, without injecting + // startSync:true the way the query-fn path does — so a bare `{ query }` never + // syncs and returns empty. React/Vue/Svelte/Solid all auto-start a config + // object; Angular requires an explicit `startSync: true` (its own config test + // passes it). Recorded until angular-db aligns. + knownGaps: [`config-object-input`], + features: { serverSnapshot: false, suspense: false }, +} + +runSuite(angularDriver) diff --git a/packages/db/tests/conformance/contract.ts b/packages/db/tests/conformance/contract.ts new file mode 100644 index 0000000000..7726f558bd --- /dev/null +++ b/packages/db/tests/conformance/contract.ts @@ -0,0 +1,149 @@ +/** + * Cross-adapter live-query conformance harness — shared contract. + * + * ONE behavioral spec for `useLiveQuery`, run against every framework adapter. + * Each adapter provides a thin `LiveQueryDriver` and the shared suite in + * `suite.ts` does the rest. + * + * Realm safety: the driver — not the scenarios — creates source collections and + * supplies query operators, both imported from the *adapter's* copy of + * `@tanstack/db`. This keeps collection instances and expression nodes in the + * same module realm as the adapter's hook, avoiding the dual-package + * `instanceof CollectionImpl` mismatch. Scenarios never import `@tanstack/db`. + * + * Expected-fail policy: `knownGaps` lists scenario KEYS this adapter does not + * yet satisfy. Populate it EMPIRICALLY — port a behavior, run it, and only add + * the key if it actually fails. The behavior matrix tells you where to look; + * the test run tells you what's broken. When a gap closes, `it.fails` errors + * ("expected to fail but passed") prompting you to delete the key. + */ +import type { Collection } from '@tanstack/db' + +/** Default row shape used by the base scenarios (a "person"). */ +export interface Row { + id: string + name: string + age: number + team: string +} + +/** + * A realm-correct source collection plus sync-driven mutators. Generic over the + * row type so relational scenarios (join, includes) can build differently-shaped + * related collections; keyed by `id` in every case. + */ +export interface SourceHandle { + collection: Collection + insert: (row: T) => void + update: (row: T) => void + remove: (row: T) => void +} + +/** + * A source whose readiness the scenario controls, for loading/eager/ready + * transitions. Starts in `loading` (synced, not ready) so scenarios can `emit` + * rows while still loading and then `markReady`. + */ +export interface DeferredSourceHandle< + T extends { id: string } = Row, +> extends SourceHandle { + /** Write rows without marking ready — exercises the eager (visible-while-loading) path. */ + emit: (rows: ReadonlyArray) => void + /** Transition the source from `loading` to `ready`. */ + markReady: () => void +} + +/** + * The subset of `@tanstack/db` query operators scenarios need, supplied by the + * driver from the adapter's realm. Grows as engine scenarios are ported. + */ +export interface DbOps { + eq: (a: any, b: any) => any + gt: (a: any, b: any) => any + count: (a: any) => any + sum: (a: any) => any + coalesce: (...args: Array) => any + /** Build an optimistic action: onMutate applies optimistic state, mutationFn confirms. */ + createOptimisticAction: (config: { + onMutate: (variables: any) => void + mutationFn: (variables: any) => Promise + }) => (variables?: any) => { isPersisted: { promise: Promise } } +} + +/** Normalized, adapter-agnostic view of a live query's current result. */ +export interface ConformanceResult { + /** Array for list queries; a single row (or undefined) for `findOne`. */ + data: any + status: string + isReady: boolean + isError: boolean + isEnabled: boolean +} + +/** A query-builder callback, e.g. `(q) => q.from({ items: source.collection })`. */ +export type QueryBuild = (q: any) => any + +/** A mounted live query under test. */ +export interface LiveQueryHandle { + current: () => ConformanceResult + /** Let the framework scheduler + core sync settle, then resolve. */ + flush: () => Promise + /** + * Run a state-mutating callback inside the framework's update scope, then + * settle (React `act`, Vue `nextTick`, Svelte `flushSync`, Solid `batch`). + * Needed when a mutation notifies synchronously, e.g. optimistic actions. + */ + apply: (fn: () => void) => Promise + unmount: () => void +} + +/** + * A mounted query whose input parameter can change after mount, for + * recompilation and disabled/enabled transitions. `setParam` re-renders with the + * new value and settles. + */ +export interface ControllableHandle

extends LiveQueryHandle { + setParam: (param: P) => Promise +} + +/** What each adapter package implements and hands to `runSuite`. */ +export interface LiveQueryDriver { + name: string + /** Operators from the adapter's `@tanstack/db` realm. */ + ops: DbOps + /** Create a realm-correct source collection + mutators, keyed by `id`. */ + makeSource: ( + initialData: ReadonlyArray, + ) => SourceHandle + /** Create a source that starts `loading` and readies on demand (keyed by `id`). */ + makeDeferredSource: () => DeferredSourceHandle + /** + * Create a pre-built live-query collection to pass straight to the hook. + * `startSync: false` yields a not-yet-syncing collection (isReady false). + */ + makePrecreated: ( + build: QueryBuild, + opts?: { startSync?: boolean }, + ) => { collection: Collection } + /** Create a source whose sync fails, driving it into `error` status. */ + makeErrorSource: () => { collection: Collection } + /** Mount a live query from a query-builder callback. */ + mount: (build: QueryBuild) => LiveQueryHandle + /** + * Mount a live query whose input depends on a parameter that can change after + * mount. `build` returns a query, or `null`/`undefined` to represent disabled. + */ + mountControllable:

( + build: (q: any, param: P) => any, + initial: P, + ) => ControllableHandle

+ /** Mount a pre-created collection passed directly to the hook. */ + mountCollection: (collection: Collection) => LiveQueryHandle + /** Mount via the config-object input form (`{ query: build }`). */ + mountConfig: (build: QueryBuild) => LiveQueryHandle + /** Mount an explicitly-disabled query (adapter's own null/undefined form). */ + mountDisabled: () => LiveQueryHandle + /** Scenario keys this adapter is empirically known NOT to satisfy yet. */ + knownGaps?: ReadonlyArray + features?: { serverSnapshot?: boolean; suspense?: boolean } +} diff --git a/packages/db/tests/conformance/suite.ts b/packages/db/tests/conformance/suite.ts new file mode 100644 index 0000000000..eb52b18260 --- /dev/null +++ b/packages/db/tests/conformance/suite.ts @@ -0,0 +1,694 @@ +/** + * Shared live-query conformance suite. + * + * Sourced bottom-up from the union of the five adapters' existing test suites + * (the "spine" + framework-agnostic "gap-closers"), plus a small tail of + * behaviors no adapter tests yet but all should (encoded as expected-fail). + * + * Each scenario has a stable KEY. An adapter marks a key in `driver.knownGaps` + * (populated empirically by running, not from the coverage matrix) to assert it + * as expected-fail. `UNIVERSAL_EXPECTED_FAIL` keys fail on every adapter until + * the underlying core gap is fixed. + * + * Coverage: query/where/select, live insert/update/delete, orderBy, join, + * groupBy/aggregate, nested aggregates, `.includes` subqueries, findOne + * cardinality, disabled + transitions, deferred readiness / eager / ready-with- + * no-data, param recompilation, optimistic mutation, pre-created & config-object + * inputs, error status, and the order-only-move tail (expected-fail). + */ +import { describe, expect, it } from 'vitest' +import type { LiveQueryDriver, LiveQueryHandle, Row } from './contract' + +const SEED: Array = [ + { id: `1`, name: `John Doe`, age: 30, team: `a` }, + { id: `2`, name: `Jane Doe`, age: 25, team: `b` }, + { id: `3`, name: `John Smith`, age: 35, team: `a` }, +] + +interface Issue { + id: string + title: string + userId: string +} + +// Issues reference SEED people: John(1) has 2, Jane(2) has 1, John Smith(3) has 0. +const ISSUES: Array = [ + { id: `i1`, title: `Issue 1`, userId: `1` }, + { id: `i2`, title: `Issue 2`, userId: `2` }, + { id: `i3`, title: `Issue 3`, userId: `1` }, +] + +/** Keys that are expected to fail on ALL adapters (core gaps, not adapter drift). */ +const UNIVERSAL_EXPECTED_FAIL = new Set([`order-only-move`]) + +export function runSuite(rawDriver: LiveQueryDriver) { + const { ops } = rawDriver + const gaps = new Set(rawDriver.knownGaps ?? []) + + // Every scenario key registered below, used to validate `knownGaps` / + // `UNIVERSAL_EXPECTED_FAIL` don't reference a stale or misspelled key. + const registeredKeys = new Set() + + // Track every handle mounted during the current scenario so it is always torn + // down, even when an (expected-fail) scenario throws before its own + // `h.unmount()`. Wrapping the driver's `mount*` methods records handles + // automatically, so scenario bodies need no `try/finally` of their own. + let mounted: Array | null = null + const track = (handle: H): H => { + mounted?.push(handle) + return handle + } + const driver: LiveQueryDriver = { + ...rawDriver, + mount: (build) => track(rawDriver.mount(build)), + mountControllable: (build, initial) => + track(rawDriver.mountControllable(build, initial)), + mountCollection: (collection) => + track(rawDriver.mountCollection(collection)), + mountConfig: (build) => track(rawDriver.mountConfig(build)), + mountDisabled: () => track(rawDriver.mountDisabled()), + } + + /** Register a scenario as `it` or `it.fails` based on known gaps. */ + const scenario = ( + key: string, + name: string, + fn: () => Promise | void, + ) => { + registeredKeys.add(key) + const expectFail = gaps.has(key) || UNIVERSAL_EXPECTED_FAIL.has(key) + const label = `[${key}] ${name}${expectFail ? ` (expected-fail)` : ``}` + const run = async () => { + const handles: Array = [] + mounted = handles + try { + await fn() + } finally { + mounted = null + for (const handle of handles) { + try { + handle.unmount() + } catch { + // teardown is best-effort / idempotent + } + } + } + } + if (expectFail) it.fails(label, run) + else it(label, run) + } + + describe(`live-query conformance :: ${driver.name}`, () => { + // ---- spine: query + liveness ---------------------------------------- + + scenario( + `basic-select`, + `from + where + select returns matching rows`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.gt(items.age, 30)) + .select(({ items }: any) => ({ id: items.id, name: items.name })), + ) + await h.flush() + + expect(h.current().data).toHaveLength(1) + expect(h.current().data[0]).toMatchObject({ + id: `3`, + name: `John Smith`, + }) + h.unmount() + }, + ) + + scenario(`live-insert`, `a sync insert appears in the result`, async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + expect(h.current().data).toHaveLength(SEED.length) + + source.insert({ id: `4`, name: `Dave`, age: 40, team: `b` }) + await h.flush() + + expect(h.current().data).toHaveLength(SEED.length + 1) + h.unmount() + }) + + scenario( + `live-delete`, + `a sync delete removes from the result`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + + source.remove(SEED[0]!) + await h.flush() + + expect(h.current().data).toHaveLength(SEED.length - 1) + h.unmount() + }, + ) + + scenario(`orderby`, `orderBy yields rows in sorted order`, async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .orderBy(({ items }: any) => items.age) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + + expect(h.current().data.map((r: any) => r.id)).toEqual([`2`, `1`, `3`]) + h.unmount() + }) + + // ---- gap-closer: cardinality (matrix: Vue tests this 0 times) -------- + + scenario( + `findone-cardinality`, + `findOne returns a single row, not an array`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.eq(items.id, `3`)) + .findOne(), + ) + await h.flush() + + expect(Array.isArray(h.current().data)).toBe(false) + expect(h.current().data).toMatchObject({ id: `3`, name: `John Smith` }) + h.unmount() + }, + ) + + // ---- gap-closer: disabled (matrix: Svelte tests this 0 times) -------- + + scenario( + `disabled-explicit`, + `a disabled query reports isEnabled=false with no data`, + async () => { + const h = driver.mountDisabled() + await h.flush() + + expect(h.current().isEnabled).toBe(false) + expect(h.current().data ?? []).toHaveLength(0) + h.unmount() + }, + ) + + // ---- spine: lifecycle invariant -------------------------------------- + + scenario( + `no-updates-after-unmount`, + `no result mutation after unmount`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + const before = h.current().data.length + + h.unmount() + source.insert({ id: `99`, name: `Zed`, age: 1, team: `b` }) + await h.flush() + + expect(h.current().data.length).toBe(before) + }, + ) + + // ---- spine: relational + aggregate queries --------------------------- + + scenario(`join`, `join across two collections`, async () => { + const people = driver.makeSource(SEED) + const issues = driver.makeSource(ISSUES) + const h = driver.mount((q) => + q + .from({ issues: issues.collection }) + .join({ persons: people.collection }, ({ issues: i, persons }: any) => + ops.eq(i.userId, persons.id), + ) + .select(({ issues: i, persons }: any) => ({ + id: i.id, + title: i.title, + name: persons.name, + })), + ) + await h.flush() + + expect(h.current().data).toHaveLength(ISSUES.length) + expect(h.current().data.find((r: any) => r.id === `i1`)).toMatchObject({ + title: `Issue 1`, + name: `John Doe`, + }) + h.unmount() + }) + + scenario( + `groupby-aggregate`, + `groupBy + count aggregates per group`, + async () => { + const people = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: people.collection }) + .groupBy(({ items }: any) => items.team) + .select(({ items }: any) => ({ + team: items.team, + count: ops.count(items.id), + })), + ) + await h.flush() + + const byTeam = new Map( + h.current().data.map((r: any) => [r.team, r.count]), + ) + expect(byTeam.get(`a`)).toBe(2) + expect(byTeam.get(`b`)).toBe(1) + h.unmount() + }, + ) + + // ---- gap-closers: engine features tested only by React today --------- + + scenario( + `nested-aggregates`, + `coalesce(count(...), 0) in a joined subquery`, + async () => { + const people = driver.makeSource(SEED) + const issues = driver.makeSource(ISSUES) + const h = driver.mount((q) => { + const issueCounts = q + .from({ issues: issues.collection }) + .groupBy(({ issues: i }: any) => i.userId) + .select(({ issues: i }: any) => ({ + userId: i.userId, + issueCount: ops.coalesce(ops.count(i.id), 0), + })) + return q + .from({ persons: people.collection }) + .leftJoin({ ic: issueCounts }, ({ persons, ic }: any) => + ops.eq(persons.id, ic.userId), + ) + .select(({ persons, ic }: any) => ({ + name: persons.name, + issueCount: ic.issueCount, + })) + }) + await h.flush() + + const byName = new Map( + h.current().data.map((r: any) => [r.name, r.issueCount]), + ) + expect(byName.get(`John Doe`)).toBe(2) + expect(byName.get(`Jane Doe`)).toBe(1) + h.unmount() + }, + ) + + scenario( + `includes-subquery`, + `select with a nested subquery produces child collections`, + async () => { + const people = driver.makeSource(SEED) + const issues = driver.makeSource(ISSUES) + const h = driver.mount((q) => + q.from({ persons: people.collection }).select(({ persons }: any) => ({ + id: persons.id, + name: persons.name, + issues: q + .from({ issues: issues.collection }) + .where(({ issues: i }: any) => ops.eq(i.userId, persons.id)) + .select(({ issues: i }: any) => ({ id: i.id, title: i.title })), + })), + ) + await h.flush() + + expect(h.current().data).toHaveLength(SEED.length) + const john = h.current().data.find((r: any) => r.id === `1`) + // `john.issues` is a child collection; read its contents through the + // collection API. John (id 1) has issues i1 and i3. + expect(john.issues).toBeDefined() + const johnIssueIds = Array.from(john.issues.values()) + .map((i: any) => i.id) + .sort() + expect(johnIssueIds).toEqual([`i1`, `i3`]) + h.unmount() + }, + ) + + // ---- free ports (no new capability needed) --------------------------- + + scenario(`live-update`, `a sync update is reflected in place`, async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id, name: items.name })), + ) + await h.flush() + + source.update({ id: `1`, name: `Johnny Doe`, age: 30, team: `a` }) + await h.flush() + + expect(h.current().data.find((r: any) => r.id === `1`).name).toBe( + `Johnny Doe`, + ) + h.unmount() + }) + + scenario( + `findone-reactive`, + `findOne updates in place and becomes undefined on delete`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.eq(items.id, `3`)) + .findOne(), + ) + await h.flush() + expect(h.current().data).toMatchObject({ name: `John Smith` }) + + source.update({ id: `3`, name: `Johnny Smith`, age: 35, team: `a` }) + await h.flush() + expect(h.current().data).toMatchObject({ name: `Johnny Smith` }) + + source.remove({ id: `3`, name: `Johnny Smith`, age: 35, team: `a` }) + await h.flush() + expect(h.current().data ?? undefined).toBeUndefined() + h.unmount() + }, + ) + + // ---- Tier 2: deferred readiness -------------------------------------- + + scenario( + `isready-transition`, + `isReady flips from false to true when the source readies`, + async () => { + const source = driver.makeDeferredSource() + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + expect(h.current().isReady).toBe(false) + + source.markReady() + await h.flush() + expect(h.current().isReady).toBe(true) + h.unmount() + }, + ) + + scenario( + `eager-visible-while-loading`, + `rows emitted before ready are visible while still loading`, + async () => { + const source = driver.makeDeferredSource() + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + + source.emit(SEED) + await h.flush() + + expect(h.current().isReady).toBe(false) + expect(h.current().data).toHaveLength(SEED.length) + + source.markReady() + await h.flush() + expect(h.current().isReady).toBe(true) + h.unmount() + }, + ) + + scenario( + `isready-no-data`, + `isReady becomes true even when the source readies with no rows`, + async () => { + const source = driver.makeDeferredSource() + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + + source.markReady() + await h.flush() + + expect(h.current().isReady).toBe(true) + expect(h.current().data ?? []).toHaveLength(0) + h.unmount() + }, + ) + + // ---- Tier 2: controllable input -------------------------------------- + + scenario( + `param-recompile`, + `changing a query parameter recompiles the result`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mountControllable( + (q, minAge) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.gt(items.age, minAge)) + .select(({ items }: any) => ({ id: items.id })), + 30, + ) + await h.flush() + expect(h.current().data).toHaveLength(1) // age > 30 → John Smith + + await h.setParam(20) + expect(h.current().data).toHaveLength(3) // all + + await h.setParam(50) + expect(h.current().data).toHaveLength(0) // none + h.unmount() + }, + ) + + scenario( + `disabled-transition`, + `disabled -> enabled -> disabled toggles correctly`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mountControllable( + (q, enabled) => + enabled + ? q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })) + : null, + false, + ) + await h.flush() + expect(h.current().isEnabled).toBe(false) + + await h.setParam(true) + expect(h.current().isEnabled).toBe(true) + expect(h.current().data).toHaveLength(SEED.length) + + await h.setParam(false) + expect(h.current().isEnabled).toBe(false) + h.unmount() + }, + ) + + // ---- Tier 2: optimistic mutation ------------------------------------- + + scenario( + `optimistic-insert`, + `optimistic insert is visible immediately, then reconciles to the server key`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + ) + await h.flush() + + const temp: Row = { id: `temp`, name: `New`, age: 20, team: `c` } + const perm: Row = { id: `p9`, name: `New`, age: 20, team: `c` } + // The "server" confirms only when we release it, so the optimistic + // window is deterministic rather than racing the settle. + let confirmServer!: () => void + const serverConfirmed = new Promise((resolve) => { + confirmServer = resolve + }) + const add = ops.createOptimisticAction({ + onMutate: () => source.collection.insert(temp), + mutationFn: async () => { + await serverConfirmed + source.remove(temp) + source.insert(perm) + }, + }) + + let tx!: { isPersisted: { promise: Promise } } + await h.apply(() => { + tx = add() + }) + // Optimistic row is visible before the server confirms. + expect(h.current().data.find((r: any) => r.id === `temp`)).toBeDefined() + + confirmServer() + await tx.isPersisted.promise + await h.flush() + // Reconciled: temp replaced by the permanent key. + expect( + h.current().data.find((r: any) => r.id === `temp`), + ).toBeUndefined() + expect(h.current().data.find((r: any) => r.id === `p9`)).toBeDefined() + h.unmount() + }, + ) + + // ---- Tier 3: input variants + error status --------------------------- + + scenario( + `precreated-collection-ready`, + `accepts a pre-created (syncing) live-query collection`, + async () => { + const source = driver.makeSource(SEED) + const pre = driver.makePrecreated( + (q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + { startSync: true }, + ) + const h = driver.mountCollection(pre.collection) + await h.flush() + + expect(h.current().isReady).toBe(true) + expect(h.current().data).toHaveLength(SEED.length) + h.unmount() + }, + ) + + scenario( + `precreated-not-syncing-isready-false`, + `a pre-created collection over a not-ready source reports isReady=false`, + async () => { + // Both the live query (startSync: false) and its source are not ready. + // Even if the adapter eagerly starts the collection on mount, it cannot + // become ready because the source never readies — so isReady stays false. + const source = driver.makeDeferredSource() + const pre = driver.makePrecreated( + (q) => + q + .from({ items: source.collection }) + .select(({ items }: any) => ({ id: items.id })), + { startSync: false }, + ) + const h = driver.mountCollection(pre.collection) + await h.flush() + expect(h.current().isReady).toBe(false) + h.unmount() + }, + ) + + scenario( + `config-object-input`, + `accepts the { query } config-object input form`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mountConfig((q) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.eq(items.id, `3`)) + .select(({ items }: any) => ({ id: items.id, name: items.name })), + ) + await h.flush() + + expect(h.current().data).toHaveLength(1) + expect(h.current().data[0]).toMatchObject({ id: `3` }) + h.unmount() + }, + ) + + scenario( + `error-status`, + `a failing source surfaces status=error / isError`, + async () => { + const source = driver.makeErrorSource() + const h = driver.mountCollection(source.collection) + await h.flush() + + expect(h.current().status).toBe(`error`) + expect(h.current().isError).toBe(true) + h.unmount() + }, + ) + + // ---- tail: universal expected-fail --------------------------- + + scenario( + `order-only-move`, + `an order-only move republishes the ordered result`, + async () => { + const source = driver.makeSource(SEED) + // Project only id+name; sort by age. Changing age reorders the result + // WITHOUT changing any projected row value. + const h = driver.mount((q) => + q + .from({ items: source.collection }) + .orderBy(({ items }: any) => items.age) + .select(({ items }: any) => ({ id: items.id, name: items.name })), + ) + await h.flush() + const first = h.current().data.map((r: any) => r.id) // ['2','1','3'] + + source.update({ id: `2`, name: `Jane Doe`, age: 99, team: `b` }) + await h.flush() + + expect(h.current().data.map((r: any) => r.id)).not.toEqual(first) + h.unmount() + }, + ) + + // ---- meta: guard against stale/misspelled expected-fail keys --------- + + it(`every knownGap / universal expected-fail references a real scenario`, () => { + for (const key of rawDriver.knownGaps ?? []) { + expect( + registeredKeys.has(key), + `${driver.name} knownGaps has "${key}", which is not a scenario key`, + ).toBe(true) + } + for (const key of UNIVERSAL_EXPECTED_FAIL) { + expect( + registeredKeys.has(key), + `UNIVERSAL_EXPECTED_FAIL has "${key}", which is not a scenario key`, + ).toBe(true) + } + }) + }) +} diff --git a/packages/react-db/tests/conformance.test.tsx b/packages/react-db/tests/conformance.test.tsx new file mode 100644 index 0000000000..303bbd89e6 --- /dev/null +++ b/packages/react-db/tests/conformance.test.tsx @@ -0,0 +1,213 @@ +/** + * React reference driver for the shared live-query conformance suite. + * + * Everything realm-sensitive — collection creation and query operators — is + * imported here (React package's `@tanstack/db`) and handed to the shared + * scenarios, so instances match what this package's `useLiveQuery` expects. + * + * `knownGaps` is populated empirically from the run below, NOT from the coverage + * matrix: only keys that actually fail belong here. Today that's just the + * universal order-only-move case (handled by the shared suite), so the list is empty. + */ +import { act, renderHook } from '@testing-library/react' +import { + coalesce, + count, + createCollection, + createLiveQueryCollection, + createOptimisticAction, + eq, + gt, + sum, +} from '@tanstack/db' +import { + mockSyncCollectionOptions, + mockSyncCollectionOptionsNoInitialState, +} from '../../db/tests/utils' +import { useLiveQuery } from '../src/useLiveQuery' +import { runSuite } from '../../db/tests/conformance/suite' +import type { RenderHookResult } from '@testing-library/react' +import type { + ConformanceResult, + ControllableHandle, + DeferredSourceHandle, + LiveQueryDriver, + QueryBuild, + SourceHandle, +} from '../../db/tests/conformance/contract' + +let sourceSeq = 0 + +function writer(collection: any) { + return (type: `insert` | `update` | `delete`, value: T) => { + collection.utils.begin() + collection.utils.write({ type, value }) + collection.utils.commit() + } +} + +function makeSource( + initialData: ReadonlyArray, +): SourceHandle { + const collection = createCollection( + mockSyncCollectionOptions({ + id: `conformance-react-${sourceSeq++}`, + getKey: (r) => r.id, + initialData: [...initialData], + }), + ) + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + } +} + +function makeDeferredSource< + T extends { id: string }, +>(): DeferredSourceHandle { + const collection = createCollection( + mockSyncCollectionOptionsNoInitialState({ + id: `conformance-react-${sourceSeq++}`, + getKey: (r) => r.id, + }), + ) + // Start sync so the sync fn binds utils and the collection sits in `loading` + // (NoInitialState never calls markReady on its own). + collection.startSyncImmediate() + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + emit: (rows) => { + collection.utils.begin() + rows.forEach((value) => collection.utils.write({ type: `insert`, value })) + collection.utils.commit() + }, + markReady: () => collection.utils.markReady(), + } +} + +function makePrecreated(build: QueryBuild, opts?: { startSync?: boolean }) { + const collection = createLiveQueryCollection({ + query: build as any, + startSync: opts?.startSync ?? true, + }) + return { collection } +} + +function makeErrorSource() { + const collection = createCollection<{ id: string }>({ + id: `conformance-react-err-${sourceSeq++}`, + getKey: (r) => r.id, + startSync: false, + sync: { + sync: () => { + throw new Error(`conformance: sync failure`) + }, + }, + }) + // Starting sync throws → engine catches and sets status to `error`. + try { + collection.startSyncImmediate() + } catch { + // expected: the rethrown sync error; status is already `error` + } + return { collection } +} + +function mount(build: QueryBuild) { + const hook = renderHook(() => useLiveQuery(build as any)) + return makeHandle(hook) +} + +function mountCollection(collection: any) { + const hook = renderHook(() => useLiveQuery(collection)) + return makeHandle(hook) +} + +function mountConfig(build: QueryBuild) { + const hook = renderHook(() => useLiveQuery({ query: build as any })) + return makeHandle(hook) +} + +function mountDisabled() { + // React's disabled convention: the query callback returns null. + const hook = renderHook(() => useLiveQuery(() => null as any)) + return makeHandle(hook) +} + +function mountControllable

( + build: (q: any, param: P) => any, + initial: P, +): ControllableHandle

{ + const hook = renderHook( + ({ param }: { param: P }) => + // Param goes in the dependency list so the hook recompiles when it changes. + useLiveQuery((q: any) => build(q, param), [param]), + { initialProps: { param: initial } }, + ) + const handle = makeHandle(hook) + return { + ...handle, + async setParam(param: P) { + await act(async () => { + hook.rerender({ param }) + }) + await handle.flush() + }, + } +} + +function makeHandle(hook: RenderHookResult) { + return { + current(): ConformanceResult { + const r: any = hook.result.current + return { + data: r?.data, + status: r?.status ?? `idle`, + isReady: Boolean(r?.isReady), + isError: Boolean(r?.isError), + // Read react-db's real `isEnabled` field so the suite catches a broken + // one (deriving from status would mask it). + isEnabled: Boolean(r?.isEnabled), + } + }, + async flush() { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)) + }) + }, + async apply(fn: () => void) { + await act(async () => { + fn() + await new Promise((resolve) => setTimeout(resolve, 0)) + }) + }, + unmount() { + hook.unmount() + }, + } +} + +const reactDriver: LiveQueryDriver = { + name: `react`, + ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + makeSource, + makeDeferredSource, + makePrecreated, + makeErrorSource, + mount, + mountControllable, + mountCollection, + mountConfig, + mountDisabled, + knownGaps: [], + features: { serverSnapshot: true, suspense: true }, +} + +runSuite(reactDriver) diff --git a/packages/solid-db/tests/conformance.test.tsx b/packages/solid-db/tests/conformance.test.tsx new file mode 100644 index 0000000000..023addafd3 --- /dev/null +++ b/packages/solid-db/tests/conformance.test.tsx @@ -0,0 +1,226 @@ +/** + * Solid driver for the shared live-query conformance suite. + * + * Each mount runs inside a `createRoot` so `unmount` disposes via the captured + * dispose fn; the root stays alive between mount and reads so Solid's reactive + * getters stay current. Solid auto-tracks signals, so controllable inputs use a + * signal read inside the query fn (no deps array). Collection/config inputs are + * passed as accessors, per Solid's arity-based input detection. + * + * `knownGaps` is populated empirically from the run below. + */ +import { + coalesce, + count, + createCollection, + createLiveQueryCollection, + createOptimisticAction, + eq, + gt, + sum, +} from '@tanstack/db' +import { createRoot, createSignal } from 'solid-js' +import { + mockSyncCollectionOptions, + mockSyncCollectionOptionsNoInitialState, +} from '../../db/tests/utils' +import { useLiveQuery } from '../src/useLiveQuery' +import { runSuite } from '../../db/tests/conformance/suite' +import type { + ConformanceResult, + ControllableHandle, + DeferredSourceHandle, + LiveQueryDriver, + LiveQueryHandle, + QueryBuild, + SourceHandle, +} from '../../db/tests/conformance/contract' + +let sourceSeq = 0 + +function writer(collection: any) { + return (type: `insert` | `update` | `delete`, value: T) => { + collection.utils.begin() + collection.utils.write({ type, value }) + collection.utils.commit() + } +} + +function makeSource( + initialData: ReadonlyArray, +): SourceHandle { + const collection = createCollection( + mockSyncCollectionOptions({ + id: `conformance-solid-${sourceSeq++}`, + getKey: (r) => r.id, + initialData: [...initialData], + }), + ) + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + } +} + +function makeDeferredSource< + T extends { id: string }, +>(): DeferredSourceHandle { + const collection = createCollection( + mockSyncCollectionOptionsNoInitialState({ + id: `conformance-solid-${sourceSeq++}`, + getKey: (r) => r.id, + }), + ) + collection.startSyncImmediate() + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + emit: (rows) => { + collection.utils.begin() + rows.forEach((value) => collection.utils.write({ type: `insert`, value })) + collection.utils.commit() + }, + markReady: () => collection.utils.markReady(), + } +} + +function makePrecreated(build: QueryBuild, opts?: { startSync?: boolean }) { + const collection = createLiveQueryCollection({ + query: build as any, + startSync: opts?.startSync ?? true, + }) + return { collection } +} + +function makeErrorSource() { + const collection = createCollection<{ id: string }>({ + id: `conformance-solid-err-${sourceSeq++}`, + getKey: (r) => r.id, + startSync: false, + sync: { + sync: () => { + throw new Error(`conformance: sync failure`) + }, + }, + }) + try { + collection.startSyncImmediate() + } catch { + // expected: engine catches the sync error and sets status to `error` + } + return { collection } +} + +async function settle() { + await new Promise((resolve) => setTimeout(resolve, 10)) +} + +function makeHandle( + getResult: () => any, + dispose: () => void, +): LiveQueryHandle { + return { + current(): ConformanceResult { + const result = getResult() + return { + data: result?.data, + status: result?.status ?? `idle`, + isReady: Boolean(result?.isReady), + isError: Boolean(result?.isError), + // solid-db exposes no `isEnabled`; derive it from status (status-derived). + isEnabled: result?.status !== `disabled`, + } + }, + flush: settle, + async apply(fn: () => void) { + fn() + await settle() + }, + unmount() { + dispose() + }, + } +} + +function inRoot(fn: () => any): { getResult: () => any; dispose: () => void } { + let result: any + let dispose!: () => void + createRoot((d) => { + dispose = d + result = fn() + }) + return { getResult: () => result, dispose } +} + +function mount(build: QueryBuild) { + const { getResult, dispose } = inRoot(() => useLiveQuery(build as any)) + return makeHandle(getResult, dispose) +} + +function mountCollection(collection: any) { + // Solid accepts a pre-created collection via an accessor. + const { getResult, dispose } = inRoot(() => useLiveQuery(() => collection)) + return makeHandle(getResult, dispose) +} + +function mountConfig(build: QueryBuild) { + // Solid accepts the config-object form via an accessor. + const { getResult, dispose } = inRoot(() => + useLiveQuery(() => ({ query: build })), + ) + return makeHandle(getResult, dispose) +} + +function mountDisabled() { + // Disabled: an accessor returning null. + const { getResult, dispose } = inRoot(() => useLiveQuery(() => null)) + return makeHandle(getResult, dispose) +} + +function mountControllable

( + build: (q: any, param: P) => any, + initial: P, +): ControllableHandle

{ + const [param, setParam] = createSignal

(initial) + const { getResult, dispose } = inRoot(() => + // Reading param() inside the query fn makes Solid recompute on change. + useLiveQuery((q: any) => build(q, param())), + ) + const handle = makeHandle(getResult, dispose) + return { + ...handle, + async setParam(next: P) { + setParam(() => next) + await settle() + }, + } +} + +const solidDriver: LiveQueryDriver = { + name: `solid`, + ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + makeSource, + makeDeferredSource, + makePrecreated, + makeErrorSource, + mount, + mountControllable, + mountCollection, + mountConfig, + mountDisabled, + // Divergence the suite surfaced: solid-db routes errors through its + // createResource/Suspense path, which THROWS (CollectionStateError) for an + // to catch, rather than exposing a readable isError flag like + // React/Vue/Svelte. Reading an errored query throws before isError can be + // observed, so the plain error-status assertion doesn't hold here. + knownGaps: [`error-status`], + features: { serverSnapshot: false, suspense: true }, +} + +runSuite(solidDriver) diff --git a/packages/svelte-db/tests/conformance.svelte.test.ts b/packages/svelte-db/tests/conformance.svelte.test.ts new file mode 100644 index 0000000000..a57709b2b9 --- /dev/null +++ b/packages/svelte-db/tests/conformance.svelte.test.ts @@ -0,0 +1,223 @@ +/** + * Svelte driver for the shared live-query conformance suite. + * + * Svelte 5 runes: each mount runs inside a persistent `$effect.root` so the + * internal `$effect` keeps updating rune state after mount; `unmount` disposes + * the root. Reads happen after `flushSync()`. Realm-sensitive pieces come from + * Svelte's `@tanstack/db`. + * + * `knownGaps` is populated empirically from the run below. + */ +import { + coalesce, + count, + createCollection, + createLiveQueryCollection, + createOptimisticAction, + eq, + gt, + sum, +} from '@tanstack/db' +import { flushSync } from 'svelte' +import { + mockSyncCollectionOptions, + mockSyncCollectionOptionsNoInitialState, +} from '../../db/tests/utils' +import { useLiveQuery } from '../src/useLiveQuery.svelte.js' +import { runSuite } from '../../db/tests/conformance/suite' +import type { + ConformanceResult, + ControllableHandle, + DeferredSourceHandle, + LiveQueryDriver, + LiveQueryHandle, + QueryBuild, + SourceHandle, +} from '../../db/tests/conformance/contract' + +let sourceSeq = 0 + +function writer(collection: any) { + return (type: `insert` | `update` | `delete`, value: T) => { + collection.utils.begin() + collection.utils.write({ type, value }) + collection.utils.commit() + } +} + +function makeSource( + initialData: ReadonlyArray, +): SourceHandle { + const collection = createCollection( + mockSyncCollectionOptions({ + id: `conformance-svelte-${sourceSeq++}`, + getKey: (r) => r.id, + initialData: [...initialData], + }), + ) + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + } +} + +function makeDeferredSource< + T extends { id: string }, +>(): DeferredSourceHandle { + const collection = createCollection( + mockSyncCollectionOptionsNoInitialState({ + id: `conformance-svelte-${sourceSeq++}`, + getKey: (r) => r.id, + }), + ) + collection.startSyncImmediate() + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + emit: (rows) => { + collection.utils.begin() + rows.forEach((value) => collection.utils.write({ type: `insert`, value })) + collection.utils.commit() + }, + markReady: () => collection.utils.markReady(), + } +} + +function makePrecreated(build: QueryBuild, opts?: { startSync?: boolean }) { + const collection = createLiveQueryCollection({ + query: build as any, + startSync: opts?.startSync ?? true, + }) + return { collection } +} + +function makeErrorSource() { + const collection = createCollection<{ id: string }>({ + id: `conformance-svelte-err-${sourceSeq++}`, + getKey: (r) => r.id, + startSync: false, + sync: { + sync: () => { + throw new Error(`conformance: sync failure`) + }, + }, + }) + try { + collection.startSyncImmediate() + } catch { + // expected: engine catches the sync error and sets status to `error` + } + return { collection } +} + +async function settle() { + flushSync() + await new Promise((resolve) => setTimeout(resolve, 10)) + flushSync() +} + +function makeHandle(getQuery: () => any, dispose: () => void): LiveQueryHandle { + return { + current(): ConformanceResult { + const query = getQuery() + return { + data: query?.data, + status: query?.status ?? `idle`, + isReady: Boolean(query?.isReady), + isError: Boolean(query?.isError), + // svelte-db exposes no `isEnabled`; derive it from status (status-derived). + isEnabled: query?.status !== `disabled`, + } + }, + flush: settle, + async apply(fn: () => void) { + fn() + await settle() + }, + unmount() { + dispose() + }, + } +} + +function mount(build: QueryBuild) { + let query: any + const dispose = $effect.root(() => { + query = useLiveQuery(build as any) + }) + return makeHandle(() => query, dispose) +} + +function mountCollection(collection: any) { + let query: any + const dispose = $effect.root(() => { + query = useLiveQuery(collection) + }) + return makeHandle(() => query, dispose) +} + +function mountConfig(build: QueryBuild) { + let query: any + const dispose = $effect.root(() => { + query = useLiveQuery({ query: build } as any) + }) + return makeHandle(() => query, dispose) +} + +function mountDisabled() { + // Svelte's disabled convention: the query callback returns null. + let query: any + const dispose = $effect.root(() => { + query = useLiveQuery(() => null as any) + }) + return makeHandle(() => query, dispose) +} + +function mountControllable

( + build: (q: any, param: P) => any, + initial: P, +): ControllableHandle

{ + let param = $state(initial) + let query: any + const dispose = $effect.root(() => { + query = useLiveQuery((q: any) => build(q, param), [() => param]) + }) + const handle = makeHandle(() => query, dispose) + return { + ...handle, + async setParam(next: P) { + param = next + await settle() + }, + } +} + +const svelteDriver: LiveQueryDriver = { + name: `svelte`, + ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + makeSource, + makeDeferredSource, + makePrecreated, + makeErrorSource, + mount, + mountControllable, + mountCollection, + mountConfig, + mountDisabled, + // Real bug the suite caught: svelte-db's `toValue()` unwrapping (added for the + // reactive `() => collection` form) CALLS a disabled query fn like `() => null` + // as if it were a getter, unwraps it to null, and falls through to + // createLiveQueryCollection({...null}) → crash in getQueryIR. The disabled + // short-circuit is unreachable for this case, and svelte-db has no disabled + // tests. Both disabled scenarios fail until this is fixed. + knownGaps: [`disabled-explicit`, `disabled-transition`], + features: { serverSnapshot: false, suspense: false }, +} + +runSuite(svelteDriver) diff --git a/packages/vue-db/tests/conformance.test.ts b/packages/vue-db/tests/conformance.test.ts new file mode 100644 index 0000000000..432876f4f5 --- /dev/null +++ b/packages/vue-db/tests/conformance.test.ts @@ -0,0 +1,219 @@ +/** + * Vue driver for the shared live-query conformance suite. + * + * Realm-sensitive pieces (collection factories, query operators) are imported + * from Vue's `@tanstack/db` and handed to the shared scenarios. Vue composables + * run inside an `effectScope` so `unmount` can dispose them via `scope.stop()`, + * which triggers the `watchEffect` `onInvalidate` cleanup. + * + * `knownGaps` is populated empirically from the run below. + */ +import { + coalesce, + count, + createCollection, + createLiveQueryCollection, + createOptimisticAction, + eq, + gt, + sum, +} from '@tanstack/db' +import { effectScope, nextTick, ref } from 'vue' +import { + mockSyncCollectionOptions, + mockSyncCollectionOptionsNoInitialState, +} from '../../db/tests/utils' +import { useLiveQuery } from '../src/useLiveQuery' +import { runSuite } from '../../db/tests/conformance/suite' +import type { + ConformanceResult, + ControllableHandle, + DeferredSourceHandle, + LiveQueryDriver, + LiveQueryHandle, + QueryBuild, + SourceHandle, +} from '../../db/tests/conformance/contract' + +let sourceSeq = 0 + +function writer(collection: any) { + return (type: `insert` | `update` | `delete`, value: T) => { + collection.utils.begin() + collection.utils.write({ type, value }) + collection.utils.commit() + } +} + +function makeSource( + initialData: ReadonlyArray, +): SourceHandle { + const collection = createCollection( + mockSyncCollectionOptions({ + id: `conformance-vue-${sourceSeq++}`, + getKey: (r) => r.id, + initialData: [...initialData], + }), + ) + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + } +} + +function makeDeferredSource< + T extends { id: string }, +>(): DeferredSourceHandle { + const collection = createCollection( + mockSyncCollectionOptionsNoInitialState({ + id: `conformance-vue-${sourceSeq++}`, + getKey: (r) => r.id, + }), + ) + collection.startSyncImmediate() + const write = writer(collection) + return { + collection, + insert: (row) => write(`insert`, row), + update: (row) => write(`update`, row), + remove: (row) => write(`delete`, row), + emit: (rows) => { + collection.utils.begin() + rows.forEach((value) => collection.utils.write({ type: `insert`, value })) + collection.utils.commit() + }, + markReady: () => collection.utils.markReady(), + } +} + +function makePrecreated(build: QueryBuild, opts?: { startSync?: boolean }) { + const collection = createLiveQueryCollection({ + query: build as any, + startSync: opts?.startSync ?? true, + }) + return { collection } +} + +function makeErrorSource() { + const collection = createCollection<{ id: string }>({ + id: `conformance-vue-err-${sourceSeq++}`, + getKey: (r) => r.id, + startSync: false, + sync: { + sync: () => { + throw new Error(`conformance: sync failure`) + }, + }, + }) + try { + collection.startSyncImmediate() + } catch { + // expected: engine catches the sync error and sets status to `error` + } + return { collection } +} + +async function settle() { + await nextTick() + await new Promise((resolve) => setTimeout(resolve, 10)) +} + +function makeHandle(result: any, scope: ReturnType) { + const handle: LiveQueryHandle = { + current(): ConformanceResult { + return { + data: result.data?.value, + status: result.status?.value ?? `idle`, + isReady: Boolean(result.isReady?.value), + isError: Boolean(result.isError?.value), + // vue-db exposes no `isEnabled`; derive it from status (status-derived). + isEnabled: result.status?.value !== `disabled`, + } + }, + flush: settle, + async apply(fn: () => void) { + fn() + await settle() + }, + unmount() { + scope.stop() + }, + } + return handle +} + +function runInScope(fn: () => R): { + result: R + scope: ReturnType +} { + const scope = effectScope() + let result!: R + scope.run(() => { + result = fn() + }) + return { result, scope } +} + +function mount(build: QueryBuild) { + const { result, scope } = runInScope(() => useLiveQuery(build as any)) + return makeHandle(result, scope) +} + +function mountCollection(collection: any) { + const { result, scope } = runInScope(() => useLiveQuery(collection)) + return makeHandle(result, scope) +} + +function mountConfig(build: QueryBuild) { + const { result, scope } = runInScope(() => + useLiveQuery({ query: build } as any), + ) + return makeHandle(result, scope) +} + +function mountDisabled() { + // Vue's disabled convention: the query callback returns undefined. + const { result, scope } = runInScope(() => + useLiveQuery(() => undefined as any), + ) + return makeHandle(result, scope) +} + +function mountControllable

( + build: (q: any, param: P) => any, + initial: P, +): ControllableHandle

{ + const param = ref(initial) as { value: P } + const { result, scope } = runInScope(() => + useLiveQuery((q: any) => build(q, param.value), [() => param.value]), + ) + const handle = makeHandle(result, scope) + return { + ...handle, + async setParam(next: P) { + param.value = next + await settle() + }, + } +} + +const vueDriver: LiveQueryDriver = { + name: `vue`, + ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + makeSource, + makeDeferredSource, + makePrecreated, + makeErrorSource, + mount, + mountControllable, + mountCollection, + mountConfig, + mountDisabled, + knownGaps: [], + features: { serverSnapshot: false, suspense: false }, +} + +runSuite(vueDriver) From 18ba740bcbab78f5ae1c31c784ed42d1dae5ba38 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Tue, 7 Jul 2026 09:29:04 -0600 Subject: [PATCH 31/58] fix(query-db-collection): keep on-demand query metadata clone-safe (#1644) * docs: design query collection persistence PR * docs: clarify RFC cluster context * docs: avoid citation syntax in RFC spec * docs: draft query collection RFC * fix(query-db-collection): keep on-demand query meta clone-safe * chore: remove local RFC drafts from PR * Apply query metadata review fixes --- .changeset/fix-query-on-demand-persistence.md | 5 ++ packages/query-db-collection/src/query.ts | 12 ++- .../query-db-collection/tests/query.test.ts | 73 ++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-query-on-demand-persistence.md diff --git a/.changeset/fix-query-on-demand-persistence.md b/.changeset/fix-query-on-demand-persistence.md new file mode 100644 index 0000000000..67fa36ce3c --- /dev/null +++ b/.changeset/fix-query-on-demand-persistence.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Keep on-demand load subset subscription state out of TanStack Query metadata so dehydrated query state remains safe to persist with structured-clone based persisters. diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 5012036127..ec3396117e 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -349,6 +349,13 @@ class QueryCollectionUtilsImpl { } } +function getLoadSubsetOptionsForMeta( + opts: LoadSubsetOptions, +): Omit { + const { subscription: _subscription, ...serializableOptions } = opts + return serializableOptions +} + /** * Creates query collection options for use with a standard Collection. * This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -1092,7 +1099,10 @@ export function queryCollectionOptions( // Generate key using common function const key = generateQueryKeyFromOptions(opts) const hashedQueryKey = hashKey(key) - const extendedMeta = { ...meta, loadSubsetOptions: opts } + const extendedMeta = { + ...meta, + loadSubsetOptions: getLoadSubsetOptionsForMeta(opts), + } const retainedEntry = metadata?.collection.get( `${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`, ) diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index db73648c19..aa9ee24063 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { QueryClient, hashKey } from '@tanstack/query-core' +import { QueryClient, dehydrate, hashKey } from '@tanstack/query-core' import { BTreeIndex, createCollection, @@ -20,6 +20,7 @@ import type { Collection, DeleteMutationFnParams, InsertMutationFnParams, + LoadSubsetOptions, SyncMetadataApi, TransactionWithMutations, UpdateMutationFnParams, @@ -5871,6 +5872,76 @@ describe(`QueryCollection`, () => { }) }) + describe(`On-demand query persistence`, () => { + const containsFunction = (value: unknown): boolean => { + if (typeof value === `function`) { + return true + } + + if (Array.isArray(value)) { + return value.some(containsFunction) + } + + if (value && typeof value === `object`) { + return Object.values(value as Record).some( + containsFunction, + ) + } + + return false + } + + it(`should keep dehydrated query state structured-clone safe after loading an on-demand subset with subscription state`, async () => { + const queryClient = new QueryClient() + const items: Array = [ + { id: `1`, name: `Item 1`, category: `A` }, + { id: `2`, name: `Item 2`, category: `B` }, + ] + + const collection = createCollection( + queryCollectionOptions({ + id: `on-demand-persistence-clone-safe-test`, + queryClient, + queryKey: [`on-demand-persistence-clone-safe-test`], + queryFn: vi.fn().mockResolvedValue(items), + getKey: (item) => item.id, + syncMode: `on-demand`, + startSync: true, + }), + ) + + try { + await collection._sync.loadSubset({ + where: eq(`category`, `A`), + subscription: { + options: { + onUnsubscribe: () => {}, + }, + } as unknown as NonNullable, + }) + + const cachedQuery = queryClient.getQueryCache().findAll()[0] + expect(cachedQuery?.meta?.loadSubsetOptions).toBeDefined() + expect( + cachedQuery?.meta?.loadSubsetOptions?.subscription, + ).toBeUndefined() + + const dehydrated = dehydrate(queryClient) + expect(dehydrated.queries.length).toBeGreaterThan(0) + expect(() => structuredClone(dehydrated)).not.toThrow() + + for (const query of dehydrated.queries) { + expect(containsFunction(query.meta)).toBe(false) + expect( + containsFunction(query.state.data as Record), + ).toBe(false) + } + } finally { + queryClient.clear() + } + }) + }) + describe(`Static queryKey with on-demand mode`, () => { it(`should automatically append serialized predicates to static queryKey in on-demand mode`, async () => { const items: Array = [ From a93abf66b1d6e2a5dcbab1f4686890eec7d41436 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 22:33:38 -0600 Subject: [PATCH 32/58] ci: Version Packages (#1651) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fix-query-on-demand-persistence.md | 5 ----- examples/electron/offline-first/package.json | 2 +- .../react-native/offline-transactions/package.json | 2 +- examples/react/offline-transactions/package.json | 2 +- examples/react/projects/package.json | 2 +- examples/react/todo/package.json | 2 +- examples/solid/todo/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 6 ++++++ packages/query-db-collection/package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 10 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 .changeset/fix-query-on-demand-persistence.md diff --git a/.changeset/fix-query-on-demand-persistence.md b/.changeset/fix-query-on-demand-persistence.md deleted file mode 100644 index 67fa36ce3c..0000000000 --- a/.changeset/fix-query-on-demand-persistence.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/query-db-collection': patch ---- - -Keep on-demand load subset subscription state out of TanStack Query metadata so dehydrated query state remains safe to persist with structured-clone based persisters. diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index b7fb2bd738..dc4462179c 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -16,7 +16,7 @@ "@tanstack/electron-db-sqlite-persistence": "^0.1.18", "@tanstack/node-db-sqlite-persistence": "^0.2.6", "@tanstack/offline-transactions": "^1.0.39", - "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/query-db-collection": "^1.0.47", "@tanstack/react-db": "^0.1.92", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 3e5583bb85..75bee00f5b 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -17,7 +17,7 @@ "@react-native-community/netinfo": "11.4.1", "@tanstack/db": "^0.6.14", "@tanstack/offline-transactions": "^1.0.39", - "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/query-db-collection": "^1.0.47", "@tanstack/react-db": "^0.1.92", "@tanstack/react-native-db-sqlite-persistence": "^0.2.6", "@tanstack/react-query": "^5.90.20", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index 9a105cd1c7..0b3f3585f3 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -11,7 +11,7 @@ "@tanstack/browser-db-sqlite-persistence": "^0.2.6", "@tanstack/db": "^0.6.14", "@tanstack/offline-transactions": "^1.0.39", - "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/query-db-collection": "^1.0.47", "@tanstack/react-db": "^0.1.92", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 301f41c85d..31d23e70ca 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,7 +17,7 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/query-db-collection": "^1.0.47", "@tanstack/react-db": "^0.1.92", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index f664674aa6..bdae873af2 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -5,7 +5,7 @@ "dependencies": { "@tanstack/electric-db-collection": "^0.3.12", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/query-db-collection": "^1.0.47", "@tanstack/react-db": "^0.1.92", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index cd4bcef55a..b77eccea9a 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -5,7 +5,7 @@ "dependencies": { "@tanstack/electric-db-collection": "^0.3.12", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.46", + "@tanstack/query-db-collection": "^1.0.47", "@tanstack/solid-db": "^0.2.28", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index 478e3d607e..6c5f66ae7d 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,11 @@ # @tanstack/query-db-collection +## 1.0.47 + +### Patch Changes + +- Keep on-demand load subset subscription state out of TanStack Query metadata so dehydrated query state remains safe to persist with structured-clone based persisters. ([#1644](https://github.com/TanStack/db/pull/1644)) + ## 1.0.46 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index ec17681dac..c0cb7df5aa 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.46", + "version": "1.0.47", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a80819102..7e230a7f55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,7 +218,7 @@ importers: specifier: ^1.0.39 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.46 + specifier: ^1.0.47 version: link:../../../packages/query-db-collection '@tanstack/react-db': specifier: ^0.1.92 @@ -306,7 +306,7 @@ importers: specifier: ^1.0.39 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.46 + specifier: ^1.0.47 version: link:../../../packages/query-db-collection '@tanstack/react-db': specifier: ^0.1.92 @@ -491,7 +491,7 @@ importers: specifier: ^1.0.39 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.46 + specifier: ^1.0.47 version: link:../../../packages/query-db-collection '@tanstack/react-db': specifier: ^0.1.92 @@ -592,7 +592,7 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.46 + specifier: ^1.0.47 version: link:../../../packages/query-db-collection '@tanstack/react-db': specifier: ^0.1.92 @@ -731,7 +731,7 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.46 + specifier: ^1.0.47 version: link:../../../packages/query-db-collection '@tanstack/react-db': specifier: ^0.1.92 @@ -852,7 +852,7 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.46 + specifier: ^1.0.47 version: link:../../../packages/query-db-collection '@tanstack/solid-db': specifier: ^0.2.28 From 6d4c096395b7ff3f428122ea8842bbead551a8c9 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 9 Jul 2026 09:29:12 +0200 Subject: [PATCH 33/58] refactor(db): extract shared live-query adapter helpers (RFC #1623 step 2) (#1641) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(db): extract shared live-query adapter helpers Add isCollection, isSingleResultCollection, and getLiveQueryStatusFlags to @tanstack/db and migrate all five adapters to use them. - isCollection: one structural, multi-realm-safe collection guard replacing the per-adapter duck-typing (React/Vue/Svelte/Angular) and Solid's `instanceof CollectionImpl` (which gives false negatives across dual-package boundaries — the same hazard the conformance suite hit). - isSingleResultCollection: shared findOne cardinality check. - getLiveQueryStatusFlags: status → {isLoading,isReady,isIdle,isError,isCleanedUp}; used by React's snapshot path (the reactive adapters derive each flag as its own signal/computed, so a shared object-returning helper doesn't fit them — that duplication is reactivity-coupled and belongs to the observer step). No behavior change; guarded by the conformance suite. First slice of the RFC #1623 extraction (step 2). Status derivation, input/disabled classification, and change→state projection remain — they are reactivity-coupled and land with the observer. Co-authored-by: Claude Opus 4.8 (1M context) --- .changeset/extract-adapter-helpers.md | 12 ++++ packages/angular-db/src/index.ts | 28 ++++---- packages/db/src/index.ts | 1 + packages/db/src/live-query-adapter.ts | 68 +++++++++++++++++++ packages/react-db/src/useLiveQuery.ts | 32 +++------ packages/solid-db/src/useLiveQuery.ts | 10 ++- packages/svelte-db/src/useLiveQuery.svelte.ts | 31 +++------ packages/vue-db/src/useLiveQuery.ts | 24 +++---- 8 files changed, 131 insertions(+), 75 deletions(-) create mode 100644 .changeset/extract-adapter-helpers.md create mode 100644 packages/db/src/live-query-adapter.ts diff --git a/.changeset/extract-adapter-helpers.md b/.changeset/extract-adapter-helpers.md new file mode 100644 index 0000000000..5f956aa12a --- /dev/null +++ b/.changeset/extract-adapter-helpers.md @@ -0,0 +1,12 @@ +--- +'@tanstack/db': minor +'@tanstack/react-db': patch +'@tanstack/vue-db': patch +'@tanstack/svelte-db': patch +'@tanstack/solid-db': patch +'@tanstack/angular-db': patch +--- + +Extract shared live-query adapter helpers into `@tanstack/db` + +Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. diff --git a/packages/angular-db/src/index.ts b/packages/angular-db/src/index.ts index 1bce5c6843..d7dc2c1c87 100644 --- a/packages/angular-db/src/index.ts +++ b/packages/angular-db/src/index.ts @@ -6,11 +6,15 @@ import { inject, signal, } from '@angular/core' -import { BaseQueryBuilder, createLiveQueryCollection } from '@tanstack/db' +import { + BaseQueryBuilder, + createLiveQueryCollection, + isCollection, + isSingleResultCollection, +} from '@tanstack/db' import type { ChangeMessage, Collection, - CollectionConfigSingleRowOption, CollectionStatus, Context, GetResult, @@ -139,14 +143,7 @@ export function injectLiveQuery(opts: any) { const collection = computed(() => { // Check if it's an existing collection - const isExistingCollection = - opts && - typeof opts === `object` && - typeof opts.subscribeChanges === `function` && - typeof opts.startSyncImmediate === `function` && - typeof opts.id === `string` - - if (isExistingCollection) { + if (isCollection(opts)) { return opts } @@ -214,10 +211,9 @@ export function injectLiveQuery(opts: any) { if (!currentCollection) { return internalData() } - const config = currentCollection.config as - | CollectionConfigSingleRowOption - | undefined - return config?.singleResult ? internalData()[0] : internalData() + return isSingleResultCollection(currentCollection) + ? internalData()[0] + : internalData() }) const syncDataFromCollection = ( @@ -282,7 +278,9 @@ export function injectLiveQuery(opts: any) { return { state, data, - collection, + // Loosely typed so the impl return stays compatible with every overload + // (the shared `isCollection` guard narrows the computed to `Collection | null`). + collection: collection as Signal, status, isLoading: computed(() => status() === `loading`), isReady: computed(() => status() === `ready` || status() === `disabled`), diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 347a3119b5..71e264d712 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -10,6 +10,7 @@ export * from './types' export * from './proxy' export * from './query/index.js' export * from './optimistic-action' +export * from './live-query-adapter' export * from './local-only' export * from './local-storage' export * from './errors' diff --git a/packages/db/src/live-query-adapter.ts b/packages/db/src/live-query-adapter.ts new file mode 100644 index 0000000000..b13001d0a8 --- /dev/null +++ b/packages/db/src/live-query-adapter.ts @@ -0,0 +1,68 @@ +import type { Collection } from './collection/index.js' +import type { CollectionStatus } from './types.js' + +/** + * Shared helpers for the first-party framework adapters (`@tanstack/react-db`, + * `@tanstack/vue-db`, `@tanstack/svelte-db`, `@tanstack/solid-db`, + * `@tanstack/angular-db`). + * + * These centralize small pieces of logic every adapter used to duplicate, so + * they stay consistent across frameworks. They are intended for the official + * adapters; treat them as unstable for external use. + */ + +/** + * Structural check for a live-query/`Collection` instance. + * + * Uses duck typing rather than `instanceof CollectionImpl` on purpose: adapters + * and core can resolve to different copies of `@tanstack/db` (dual-package / + * multi-realm), where `instanceof` gives false negatives. The three methods + * below uniquely identify a Collection. + */ +export function isCollection( + value: unknown, +): value is Collection { + return ( + typeof value === `object` && + value !== null && + typeof (value as any).subscribeChanges === `function` && + typeof (value as any).startSyncImmediate === `function` && + typeof (value as any).id === `string` + ) +} + +/** Whether a collection yields a single result (`findOne`) rather than an array. */ +export function isSingleResultCollection( + collection: Collection, +): boolean { + return ( + (collection.config as { singleResult?: boolean } | undefined) + ?.singleResult === true + ) +} + +/** The derived boolean status flags every adapter exposes for a query. */ +export interface LiveQueryStatusFlags { + isLoading: boolean + isReady: boolean + isIdle: boolean + isError: boolean + isCleanedUp: boolean +} + +/** + * Derive the boolean status flags from a collection status. Adapters represent + * a disabled query separately (with `isReady: true`); this covers the real + * `CollectionStatus` values. + */ +export function getLiveQueryStatusFlags( + status: CollectionStatus, +): LiveQueryStatusFlags { + return { + isLoading: status === `loading`, + isReady: status === `ready`, + isIdle: status === `idle`, + isError: status === `error`, + isCleanedUp: status === `cleaned-up`, + } +} diff --git a/packages/react-db/src/useLiveQuery.ts b/packages/react-db/src/useLiveQuery.ts index 2291c2019a..8dc3d0a31b 100644 --- a/packages/react-db/src/useLiveQuery.ts +++ b/packages/react-db/src/useLiveQuery.ts @@ -1,12 +1,13 @@ import { useRef, useSyncExternalStore } from 'react' import { BaseQueryBuilder, - CollectionImpl, createLiveQueryCollection, + getLiveQueryStatusFlags, + isCollection, + isSingleResultCollection, } from '@tanstack/db' import type { Collection, - CollectionConfigSingleRowOption, CollectionStatus, Context, GetResult, @@ -317,13 +318,8 @@ export function useLiveQuery( configOrQueryOrCollection: any, deps: Array = [], ) { - // Check if it's already a collection by checking for specific collection methods - const isCollection = - configOrQueryOrCollection && - typeof configOrQueryOrCollection === `object` && - typeof configOrQueryOrCollection.subscribeChanges === `function` && - typeof configOrQueryOrCollection.startSyncImmediate === `function` && - typeof configOrQueryOrCollection.id === `string` + // Check if it's already a collection + const inputIsCollection = isCollection(configOrQueryOrCollection) // Use refs to cache collection and track dependencies const collectionRef = useRef | null>( @@ -342,14 +338,14 @@ export function useLiveQuery( // Check if we need to create/recreate the collection const needsNewCollection = !collectionRef.current || - (isCollection && configRef.current !== configOrQueryOrCollection) || - (!isCollection && + (inputIsCollection && configRef.current !== configOrQueryOrCollection) || + (!inputIsCollection && (depsRef.current === null || depsRef.current.length !== deps.length || depsRef.current.some((dep, i) => dep !== deps[i]))) if (needsNewCollection) { - if (isCollection) { + if (inputIsCollection) { // Warn when passing a collection directly with on-demand sync mode // In on-demand mode, data is only loaded when queries with predicates request it // Passing the collection directly doesn't provide any predicates, so no data loads @@ -379,7 +375,7 @@ export function useLiveQuery( if (result === undefined || result === null) { // Callback returned undefined/null - disabled query collectionRef.current = null - } else if (result instanceof CollectionImpl) { + } else if (isCollection(result)) { // Callback returned a Collection instance - use it directly result.startSyncImmediate() collectionRef.current = result @@ -527,9 +523,7 @@ export function useLiveQuery( } else { // Capture a stable view of entries for this snapshot to avoid tearing const entries = Array.from(snapshot.collection.entries()) - const config: CollectionConfigSingleRowOption = - snapshot.collection.config - const singleResult = config.singleResult + const singleResult = isSingleResultCollection(snapshot.collection) let stateCache: Map | null = null let dataCache: Array | null = null @@ -548,11 +542,7 @@ export function useLiveQuery( }, collection: snapshot.collection, status: snapshot.collection.status, - isLoading: snapshot.collection.status === `loading`, - isReady: snapshot.collection.status === `ready`, - isIdle: snapshot.collection.status === `idle`, - isError: snapshot.collection.status === `error`, - isCleanedUp: snapshot.collection.status === `cleaned-up`, + ...getLiveQueryStatusFlags(snapshot.collection.status), isEnabled: true, } } diff --git a/packages/solid-db/src/useLiveQuery.ts b/packages/solid-db/src/useLiveQuery.ts index 5380bae00d..7759915f32 100644 --- a/packages/solid-db/src/useLiveQuery.ts +++ b/packages/solid-db/src/useLiveQuery.ts @@ -9,15 +9,15 @@ import { import { ReactiveMap } from '@solid-primitives/map' import { BaseQueryBuilder, - CollectionImpl, createLiveQueryCollection, + isCollection, + isSingleResultCollection, } from '@tanstack/db' import { createStore, reconcile } from 'solid-js/store' import type { Accessor } from 'solid-js' import type { ChangeMessage, Collection, - CollectionConfigSingleRowOption, CollectionStatus, Context, GetResult, @@ -311,7 +311,7 @@ export function useLiveQuery( return null } - if (innerCollection instanceof CollectionImpl) { + if (isCollection(innerCollection)) { innerCollection.startSyncImmediate() return innerCollection as Collection } @@ -426,9 +426,7 @@ export function useLiveQuery( function getData() { const currentCollection = collection() if (currentCollection) { - const config: CollectionConfigSingleRowOption = - currentCollection.config - if (config.singleResult) { + if (isSingleResultCollection(currentCollection)) { // Force resource tracking so Suspense works getDataResource() return data[0] diff --git a/packages/svelte-db/src/useLiveQuery.svelte.ts b/packages/svelte-db/src/useLiveQuery.svelte.ts index 76dedd3c78..789d3d08f8 100644 --- a/packages/svelte-db/src/useLiveQuery.svelte.ts +++ b/packages/svelte-db/src/useLiveQuery.svelte.ts @@ -2,11 +2,15 @@ import { untrack } from 'svelte' // eslint-disable-next-line import/no-duplicates -- See https://github.com/un-ts/eslint-plugin-import-x/issues/308 import { SvelteMap } from 'svelte/reactivity' -import { BaseQueryBuilder, createLiveQueryCollection } from '@tanstack/db' +import { + BaseQueryBuilder, + createLiveQueryCollection, + isCollection, + isSingleResultCollection, +} from '@tanstack/db' import type { ChangeMessage, Collection, - CollectionConfigSingleRowOption, CollectionStatus, Context, GetResult, @@ -301,14 +305,9 @@ export function useLiveQuery( } // Check if it's already a collection by checking for specific collection methods - const isCollection = - unwrappedParam && - typeof unwrappedParam === `object` && - typeof unwrappedParam.subscribeChanges === `function` && - typeof unwrappedParam.startSyncImmediate === `function` && - typeof unwrappedParam.id === `string` - - if (isCollection) { + const inputIsCollection = isCollection(unwrappedParam) + + if (inputIsCollection) { // Warn when passing a collection directly with on-demand sync mode // In on-demand mode, data is only loaded when queries with predicates request it // Passing the collection directly doesn't provide any predicates, so no data loads @@ -474,16 +473,8 @@ export function useLiveQuery( }, get data() { const currentCollection = collection - if (currentCollection) { - const config = - currentCollection.config as CollectionConfigSingleRowOption< - any, - any, - any - > - if (config.singleResult) { - return internalData[0] - } + if (currentCollection && isSingleResultCollection(currentCollection)) { + return internalData[0] } return internalData }, diff --git a/packages/vue-db/src/useLiveQuery.ts b/packages/vue-db/src/useLiveQuery.ts index 479c92b2b0..762cbda0a6 100644 --- a/packages/vue-db/src/useLiveQuery.ts +++ b/packages/vue-db/src/useLiveQuery.ts @@ -8,11 +8,14 @@ import { toValue, watchEffect, } from 'vue' -import { createLiveQueryCollection } from '@tanstack/db' +import { + createLiveQueryCollection, + isCollection, + isSingleResultCollection, +} from '@tanstack/db' import type { ChangeMessage, Collection, - CollectionConfigSingleRowOption, CollectionStatus, Context, GetResult, @@ -265,15 +268,10 @@ export function useLiveQuery( } } - // Check if it's already a collection by checking for specific collection methods - const isCollection = - unwrappedParam && - typeof unwrappedParam === `object` && - typeof unwrappedParam.subscribeChanges === `function` && - typeof unwrappedParam.startSyncImmediate === `function` && - typeof unwrappedParam.id === `string` + // Check if it's already a collection + const inputIsCollection = isCollection(unwrappedParam) - if (isCollection) { + if (inputIsCollection) { // Warn when passing a collection directly with on-demand sync mode // In on-demand mode, data is only loaded when queries with predicates request it // Passing the collection directly doesn't provide any predicates, so no data loads @@ -347,9 +345,9 @@ export function useLiveQuery( if (!currentCollection) { return internalData } - const config: CollectionConfigSingleRowOption = - currentCollection.config - return config.singleResult ? internalData[0] : internalData + return isSingleResultCollection(currentCollection) + ? internalData[0] + : internalData }) // Track collection status reactively From b2f305a1af108121ad79cd2f4107c1ed41b9911c Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 9 Jul 2026 09:33:13 +0200 Subject: [PATCH 34/58] fix(svelte-db): disabled useLiveQuery no longer crashes (#1637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A disabled query (callback returning null/undefined) crashed: the reactive-getter unwrapping (`toValue`) called the callback, and the resulting null was spread into createLiveQueryCollection → throw in getQueryIR. Now a null/undefined unwrapped value is treated as a disabled query, matching React/Vue/Solid/Angular. Clears the svelte-db conformance knownGaps (disabled-explicit, disabled-transition); both now pass as normal tests. Co-authored-by: Claude Opus 4.8 (1M context) --- .changeset/svelte-disabled-query-fix.md | 7 +++++++ packages/svelte-db/src/useLiveQuery.svelte.ts | 7 +++++++ packages/svelte-db/tests/conformance.svelte.test.ts | 8 +------- 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 .changeset/svelte-disabled-query-fix.md diff --git a/.changeset/svelte-disabled-query-fix.md b/.changeset/svelte-disabled-query-fix.md new file mode 100644 index 0000000000..1b4892d8db --- /dev/null +++ b/.changeset/svelte-disabled-query-fix.md @@ -0,0 +1,7 @@ +--- +'@tanstack/svelte-db': patch +--- + +fix(svelte-db): a disabled `useLiveQuery` (query callback returning `null`/`undefined`) no longer crashes + +The reactive-getter unwrapping (`toValue`) called the query callback and, when it returned `null`/`undefined` to signal a disabled query, passed the unwrapped `null` into `createLiveQueryCollection`, throwing in `getQueryIR`. A `null`/`undefined` resolved value is now treated as a disabled query (returns the `disabled` state) as it is in the other adapters. diff --git a/packages/svelte-db/src/useLiveQuery.svelte.ts b/packages/svelte-db/src/useLiveQuery.svelte.ts index 789d3d08f8..0d9dd9d7fe 100644 --- a/packages/svelte-db/src/useLiveQuery.svelte.ts +++ b/packages/svelte-db/src/useLiveQuery.svelte.ts @@ -349,6 +349,13 @@ export function useLiveQuery( startSync: true, }) } else { + // A reactive getter (or param-driven query fn) can resolve to null/undefined + // to mean "disabled". `toValue` above already called it, so guard here — + // otherwise `{ ...null }` reaches createLiveQueryCollection and throws. + if (unwrappedParam === undefined || unwrappedParam === null) { + return null + } + return createLiveQueryCollection({ ...unwrappedParam, startSync: true, diff --git a/packages/svelte-db/tests/conformance.svelte.test.ts b/packages/svelte-db/tests/conformance.svelte.test.ts index a57709b2b9..5b502b02c7 100644 --- a/packages/svelte-db/tests/conformance.svelte.test.ts +++ b/packages/svelte-db/tests/conformance.svelte.test.ts @@ -210,13 +210,7 @@ const svelteDriver: LiveQueryDriver = { mountCollection, mountConfig, mountDisabled, - // Real bug the suite caught: svelte-db's `toValue()` unwrapping (added for the - // reactive `() => collection` form) CALLS a disabled query fn like `() => null` - // as if it were a getter, unwraps it to null, and falls through to - // createLiveQueryCollection({...null}) → crash in getQueryIR. The disabled - // short-circuit is unreachable for this case, and svelte-db has no disabled - // tests. Both disabled scenarios fail until this is fixed. - knownGaps: [`disabled-explicit`, `disabled-transition`], + knownGaps: [], features: { serverSnapshot: false, suspense: false }, } From 42fb59ced1cbd1c32b4cd05294d5812603b0c917 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 9 Jul 2026 09:34:43 +0200 Subject: [PATCH 35/58] fix(angular-db): config-object input to injectLiveQuery now syncs (#1638) The `{ query }` config-object branch called createLiveQueryCollection(opts) without defaulting startSync, unlike the query-fn and reactive-options branches (which force startSync: true). A bare `{ query }` therefore never synced and returned empty. Default startSync: true and gcTime: 0 while honoring explicit values in the config. Clears the angular-db conformance knownGap (config-object-input). Co-authored-by: Claude Opus 4.8 (1M context) --- .changeset/angular-config-object-startsync.md | 7 +++++++ packages/angular-db/src/index.ts | 6 ++++-- packages/angular-db/tests/conformance.test.ts | 8 +------- 3 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 .changeset/angular-config-object-startsync.md diff --git a/.changeset/angular-config-object-startsync.md b/.changeset/angular-config-object-startsync.md new file mode 100644 index 0000000000..20c2486093 --- /dev/null +++ b/.changeset/angular-config-object-startsync.md @@ -0,0 +1,7 @@ +--- +'@tanstack/angular-db': patch +--- + +fix(angular-db): a `{ query }` config-object passed to `injectLiveQuery` now syncs + +The config-object branch called `createLiveQueryCollection(opts)` without defaulting `startSync`, unlike the query-function and reactive-options branches (which force `startSync: true`), so a bare `{ query }` never started syncing and produced no data. It now defaults `startSync: true` and `gcTime: 0` while still honoring any explicit values in the config. diff --git a/packages/angular-db/src/index.ts b/packages/angular-db/src/index.ts index d7dc2c1c87..f8cac4086b 100644 --- a/packages/angular-db/src/index.ts +++ b/packages/angular-db/src/index.ts @@ -191,9 +191,11 @@ export function injectLiveQuery(opts: any) { }) } - // Handle LiveQueryCollectionConfig objects + // Handle LiveQueryCollectionConfig objects. Default startSync/gcTime to + // match the query-fn and reactive-options paths, but let an explicit value + // in the config win — otherwise a bare `{ query }` never syncs. if (opts && typeof opts === `object` && typeof opts.query === `function`) { - return createLiveQueryCollection(opts) + return createLiveQueryCollection({ startSync: true, gcTime: 0, ...opts }) } throw new Error(`Invalid options provided to injectLiveQuery`) diff --git a/packages/angular-db/tests/conformance.test.ts b/packages/angular-db/tests/conformance.test.ts index b60b8fa3d1..a8cc7ec803 100644 --- a/packages/angular-db/tests/conformance.test.ts +++ b/packages/angular-db/tests/conformance.test.ts @@ -214,13 +214,7 @@ const angularDriver: LiveQueryDriver = { mountCollection, mountConfig, mountDisabled, - // Divergence the suite surfaced: angular-db's plain `{ query }` config-object - // path calls createLiveQueryCollection(opts) as-is, without injecting - // startSync:true the way the query-fn path does — so a bare `{ query }` never - // syncs and returns empty. React/Vue/Svelte/Solid all auto-start a config - // object; Angular requires an explicit `startSync: true` (its own config test - // passes it). Recorded until angular-db aligns. - knownGaps: [`config-object-input`], + knownGaps: [], features: { serverSnapshot: false, suspense: false }, } From f3ade65e19633bf20704eb60e06a8a7c68e1228e Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 9 Jul 2026 09:36:28 +0200 Subject: [PATCH 36/58] test(conformance): parametrize how adapters surface query errors (#1639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solid surfaces query errors by throwing (CollectionStateError) on read, for an to catch, rather than exposing a readable isError flag like React/Vue/Svelte/Angular. That's a framework idiom, not a bug — so instead of forcing Solid into the flag model, parametrize the error-status scenario. Adds driver.errorSurface ('flag' | 'throw', default 'flag'). The error-status scenario asserts a readable isError/status for 'flag', and that reading the errored result throws for 'throw'. Solid declares 'throw' and clears its error-status knownGap; the other four keep the default flag assertions. Co-authored-by: Claude Opus 4.8 (1M context) --- packages/db/tests/conformance/contract.ts | 7 +++++++ packages/db/tests/conformance/suite.ts | 12 +++++++++--- packages/solid-db/tests/conformance.test.tsx | 12 ++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/db/tests/conformance/contract.ts b/packages/db/tests/conformance/contract.ts index 7726f558bd..486fb4920d 100644 --- a/packages/db/tests/conformance/contract.ts +++ b/packages/db/tests/conformance/contract.ts @@ -145,5 +145,12 @@ export interface LiveQueryDriver { mountDisabled: () => LiveQueryHandle /** Scenario keys this adapter is empirically known NOT to satisfy yet. */ knownGaps?: ReadonlyArray + /** + * How the adapter surfaces a query error (see the `error-status` scenario): + * - `flag` (default): a readable `isError`/`status === 'error'` on the result. + * - `throw`: reading the errored result throws, for a framework error boundary + * to catch (e.g. Solid's `createResource`/`` model). + */ + errorSurface?: `flag` | `throw` features?: { serverSnapshot?: boolean; suspense?: boolean } } diff --git a/packages/db/tests/conformance/suite.ts b/packages/db/tests/conformance/suite.ts index eb52b18260..8e02911c69 100644 --- a/packages/db/tests/conformance/suite.ts +++ b/packages/db/tests/conformance/suite.ts @@ -636,14 +636,20 @@ export function runSuite(rawDriver: LiveQueryDriver) { scenario( `error-status`, - `a failing source surfaces status=error / isError`, + `a failing source surfaces an error (flag or boundary)`, async () => { const source = driver.makeErrorSource() const h = driver.mountCollection(source.collection) await h.flush() - expect(h.current().status).toBe(`error`) - expect(h.current().isError).toBe(true) + if (driver.errorSurface === `throw`) { + // Boundary model: reading the errored result throws (for an error + // boundary to catch), rather than exposing a readable flag. + expect(() => h.current()).toThrow() + } else { + expect(h.current().status).toBe(`error`) + expect(h.current().isError).toBe(true) + } h.unmount() }, ) diff --git a/packages/solid-db/tests/conformance.test.tsx b/packages/solid-db/tests/conformance.test.tsx index 023addafd3..2dcea6ab03 100644 --- a/packages/solid-db/tests/conformance.test.tsx +++ b/packages/solid-db/tests/conformance.test.tsx @@ -214,12 +214,12 @@ const solidDriver: LiveQueryDriver = { mountCollection, mountConfig, mountDisabled, - // Divergence the suite surfaced: solid-db routes errors through its - // createResource/Suspense path, which THROWS (CollectionStateError) for an - // to catch, rather than exposing a readable isError flag like - // React/Vue/Svelte. Reading an errored query throws before isError can be - // observed, so the plain error-status assertion doesn't hold here. - knownGaps: [`error-status`], + // solid-db routes errors through its createResource/Suspense path: reading an + // errored query throws (CollectionStateError) for an to catch, + // rather than exposing a readable isError flag. That's a framework idiom, not a + // gap — the error-status scenario is parametrized to assert it via the boundary. + errorSurface: `throw`, + knownGaps: [], features: { serverSnapshot: false, suspense: true }, } From e1477210490fbe8d77195cdb80fb326710b59f60 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 9 Jul 2026 09:23:39 -0600 Subject: [PATCH 37/58] test(query-db-collection): cover query invalidation behavior (#1655) * test(query-db-collection): cover invalidation behavior matrix * chore: add query invalidation changeset * ci: apply automated fixes * test(query-db-collection): clean up invalidation observers --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/query-invalidation-matrix.md | 5 + .../query-db-collection/tests/query.test.ts | 411 ++++++++++++++++++ 2 files changed, 416 insertions(+) create mode 100644 .changeset/query-invalidation-matrix.md diff --git a/.changeset/query-invalidation-matrix.md b/.changeset/query-invalidation-matrix.md new file mode 100644 index 0000000000..9737b7e0ec --- /dev/null +++ b/.changeset/query-invalidation-matrix.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Add coverage for query invalidation behavior across eager and on-demand query collections. diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index aa9ee24063..1dd6e21c8d 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -1606,6 +1606,417 @@ describe(`QueryCollection`, () => { }) }) + describe(`invalidation behavior`, () => { + it(`rematerializes an active eager query after exact invalidation`, async () => { + const queryKey = [`invalidation-exact-eager-test`] + let items: Array = [{ id: `1`, name: `Item 1` }] + const queryFn = vi.fn().mockImplementation(() => Promise.resolve(items)) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-exact-eager-test`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + }), + ) + + try { + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Item 1`, + }) + }) + + items = [{ id: `1`, name: `Updated Item 1` }] + await queryClient.invalidateQueries({ queryKey, exact: true }) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(2) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Updated Item 1`, + }) + }) + } finally { + await collection.cleanup() + } + + expect( + queryClient.getQueryCache().find({ queryKey })?.getObserversCount(), + ).toBe(0) + }) + + it(`rematerializes an active eager query after prefix invalidation`, async () => { + const rootQueryKey = [`invalidation-prefix-eager-test`] + const queryKey = [...rootQueryKey, `child`] + let items: Array = [{ id: `1`, name: `Item 1` }] + const queryFn = vi.fn().mockImplementation(() => Promise.resolve(items)) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-prefix-eager-test`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + }), + ) + + try { + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Item 1`, + }) + }) + + items = [{ id: `1`, name: `Updated Item 1` }] + await queryClient.invalidateQueries({ queryKey: rootQueryKey }) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(2) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Updated Item 1`, + }) + }) + } finally { + await collection.cleanup() + } + }) + + it(`rematerializes an active on-demand subset after exact invalidation`, async () => { + const queryKey = [`invalidation-exact-on-demand-test`] + let items: Array = [{ id: `1`, name: `Item 1` }] + const queryFn = vi.fn().mockImplementation(() => Promise.resolve(items)) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-exact-on-demand-test`, + queryClient, + queryKey, + queryFn, + getKey, + syncMode: `on-demand`, + }), + ) + const liveQuery = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .select(({ item }) => ({ id: item.id, name: item.name })), + }) + + try { + await liveQuery.preload() + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Item 1`, + }) + }) + + items = [{ id: `1`, name: `Updated Item 1` }] + await queryClient.invalidateQueries({ queryKey, exact: true }) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(2) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Updated Item 1`, + }) + }) + } finally { + await liveQuery.cleanup() + } + }) + + it(`rematerializes an active on-demand subset after root prefix invalidation`, async () => { + const queryKey = [`invalidation-prefix-on-demand-test`] + let items: Array = [{ id: `1`, name: `Item 1` }] + const observedQueryKeys: Array> = [] + const queryFn = vi + .fn() + .mockImplementation( + (ctx: QueryFunctionContext>) => { + observedQueryKeys.push(ctx.queryKey) + return Promise.resolve(items) + }, + ) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-prefix-on-demand-test`, + queryClient, + queryKey, + queryFn, + getKey, + syncMode: `on-demand`, + }), + ) + const liveQuery = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => eq(item.id, `1`)) + .select(({ item }) => ({ id: item.id, name: item.name })), + }) + + try { + await liveQuery.preload() + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(observedQueryKeys[0]?.length).toBeGreaterThan( + queryKey.length, + ) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Item 1`, + }) + }) + + items = [{ id: `1`, name: `Updated Item 1` }] + await queryClient.invalidateQueries({ queryKey }) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(2) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Updated Item 1`, + }) + }) + } finally { + await liveQuery.cleanup() + } + }) + + it(`keeps overlapping on-demand subset rows materialized when one subset is invalidated`, async () => { + const queryKey = [`invalidation-overlap-on-demand-test`] + let firstSubset: Array = [ + { id: `1`, name: `Item 1` }, + { id: `2`, name: `Shared Item` }, + ] + const secondSubset: Array = [ + { id: `2`, name: `Shared Item` }, + { id: `3`, name: `Item 3` }, + ] + const observedQueryKeys: Array> = [] + const queryFn = vi + .fn() + .mockImplementation( + (ctx: QueryFunctionContext>) => { + const firstObservedKey = observedQueryKeys[0] + observedQueryKeys.push(ctx.queryKey) + return Promise.resolve( + firstObservedKey === undefined || + hashKey(ctx.queryKey) === hashKey(firstObservedKey) + ? firstSubset + : secondSubset, + ) + }, + ) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-overlap-on-demand-test`, + queryClient, + queryKey, + queryFn, + getKey, + syncMode: `on-demand`, + }), + ) + const firstLiveQuery = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => inArray(item.id, [`1`, `2`])) + .select(({ item }) => ({ id: item.id, name: item.name })), + }) + const secondLiveQuery = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => inArray(item.id, [`2`, `3`])) + .select(({ item }) => ({ id: item.id, name: item.name })), + }) + + try { + await firstLiveQuery.preload() + await secondLiveQuery.preload() + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(2) + expect(collection.size).toBe(3) + }) + + firstSubset = [ + { id: `1`, name: `Updated Item 1` }, + { id: `2`, name: `Updated Shared Item` }, + ] + await queryClient.invalidateQueries({ + queryKey: observedQueryKeys[0], + exact: true, + }) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(3) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Updated Item 1`, + }) + expect(stripVirtualProps(collection.get(`2`))).toEqual({ + id: `2`, + name: `Updated Shared Item`, + }) + }) + expect(stripVirtualProps(collection.get(`3`))).toEqual({ + id: `3`, + name: `Item 3`, + }) + } finally { + await firstLiveQuery.cleanup() + await secondLiveQuery.cleanup() + } + }) + + it(`retains existing rows when an invalidation refetch fails`, async () => { + const queryKey = [`invalidation-failed-refetch-test`] + const initialItems: Array = [{ id: `1`, name: `Item 1` }] + const refetchError = new Error(`refetch failed`) + const queryFn = vi + .fn() + .mockResolvedValueOnce(initialItems) + .mockRejectedValueOnce(refetchError) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-failed-refetch-test`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + retry: false, + }), + ) + + try { + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Item 1`, + }) + }) + + await queryClient.invalidateQueries({ queryKey, exact: true }) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(2) + expect(collection.utils.lastError).toBe(refetchError) + }) + expect(collection.size).toBe(1) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Item 1`, + }) + } finally { + await collection.cleanup() + } + }) + + // Retained/persisted invalidation is intentionally not covered here: the + // existing unit fixtures do not exercise the full persisted retention path + // without introducing broader persistence setup. This PR characterizes active, + // inactive, removed, overlapping, and failed-refetch behavior first. + it(`does not rematerialize inactive cached query rows after invalidation`, async () => { + const retainedQueryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 0, + gcTime: 60_000, + retry: false, + }, + }, + }) + const queryKey = [`invalidation-inactive-cached-test`] + let items: Array = [{ id: `1`, name: `Item 1` }] + const queryFn = vi.fn().mockImplementation(() => Promise.resolve(items)) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-inactive-cached-test`, + queryClient: retainedQueryClient, + queryKey, + queryFn, + getKey, + startSync: true, + }), + ) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + await collection.cleanup() + expect(collection.status).toBe(`cleaned-up`) + expect( + retainedQueryClient.getQueryCache().find({ queryKey }), + ).toBeDefined() + + items = [{ id: `1`, name: `Updated Item 1` }] + await retainedQueryClient.invalidateQueries({ queryKey, exact: true }) + + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(0) + retainedQueryClient.clear() + }) + + it(`does not refetch a removed query after invalidation`, async () => { + const queryKey = [`invalidation-removed-query-test`] + let items: Array = [{ id: `1`, name: `Item 1` }] + const queryFn = vi.fn().mockImplementation(() => Promise.resolve(items)) + + const collection = createCollection( + queryCollectionOptions({ + id: `invalidation-removed-query-test`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + }), + ) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + await collection.cleanup() + queryClient.removeQueries({ queryKey, exact: true }) + expect(queryClient.getQueryCache().find({ queryKey })).toBeUndefined() + + items = [{ id: `1`, name: `Updated Item 1` }] + await queryClient.invalidateQueries({ queryKey, exact: true }) + + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(0) + }) + }) + it(`should handle concurrent query operations`, async () => { const queryKey = [`concurrent-test`] const items = [{ id: `1`, name: `Item 1` }] From b2b8923e0a4906b39e91a0e6c967e7d63b0ae1a5 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 9 Jul 2026 09:23:50 -0600 Subject: [PATCH 38/58] docs(query-db-collection): clarify select row extraction (#1654) * docs: add query select semantics design * docs(query-db-collection): clarify select row extraction * Apply review simplifications * Add changeset for query select semantics * Strengthen select cache preservation test --- .changeset/clarify-query-select-semantics.md | 5 +++ docs/collections/query-collection.md | 41 ++++++++++++++++++- packages/query-db-collection/src/query.ts | 5 ++- .../query-db-collection/tests/query.test.ts | 40 ++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 .changeset/clarify-query-select-semantics.md diff --git a/.changeset/clarify-query-select-semantics.md b/.changeset/clarify-query-select-semantics.md new file mode 100644 index 0000000000..632c15c281 --- /dev/null +++ b/.changeset/clarify-query-select-semantics.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Clarify that `select` extracts rows for DB materialization while preserving the wrapped TanStack Query cache response. diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 73f3511edc..825087cf65 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -56,7 +56,7 @@ The `queryCollectionOptions` function accepts the following options: ### Query Options -- `select`: Function that lets extract array items when they're wrapped with metadata +- `select`: Function that extracts the row array TanStack DB materializes from a wrapped Query response - `enabled`: Whether the query should automatically run (default: `true`) - `refetchInterval`: Refetch interval in milliseconds (default: 0 — set an interval to enable polling refetching) - `retry`: Retry configuration for failed queries @@ -96,6 +96,45 @@ const todosCollection = createCollection( If `queryFn` is missing at runtime, `queryCollectionOptions` throws `QueryFnRequiredError`. +### Selecting Rows from Wrapped Responses + +Many APIs return rows inside a response envelope that also contains metadata such as pagination cursors, totals, or request information. Use `select` to extract the row array that TanStack DB should materialize: + +```typescript +interface TodosResponse { + items: Array<{ id: string; title: string }> + nextCursor?: string + total: number +} + +const todosCollection = createCollection( + queryCollectionOptions({ + queryKey: ["todos"], + queryFn: async (): Promise => { + const response = await fetch("/api/todos") + return response.json() + }, + select: (response) => response.items, + queryClient, + getKey: (item) => item.id, + }), +) +``` + +`select` is a query-db-collection row extraction hook. It tells TanStack DB which rows to materialize while the TanStack Query cache keeps the original query response shape. In the example above, `queryClient.getQueryData(["todos"])` still returns the full `TodosResponse`, including `nextCursor` and `total`. + +This differs from TanStack Query's observer-level `select`: query-db-collection uses this option to bridge Query's response object into DB's normalized row store. + +Direct write utilities such as `writeInsert`, `writeUpdate`, and `writeDelete` make a best-effort attempt to update the matching row array inside wrapped Query cache entries while preserving wrapper metadata. + +This works automatically for simple wrappers such as: + +- `{ data: [...] }` +- `{ items: [...] }` +- `{ results: [...] }` + +Derived projections, such as `select: (response) => response.edges.map((edge) => edge.node)`, are read-side row extraction only. query-db-collection cannot generally reconstruct the original response envelope from updated rows. Refetch or invalidate the query if the wrapped cache must exactly reflect direct writes for a derived projection. + ### Collection Options - `id`: Unique identifier for the collection diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index ec3396117e..ea303b8ddb 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -77,7 +77,10 @@ export interface QueryCollectionConfig< ) => Promise> | Array ? (context: QueryFunctionContext) => Promise> | Array : TQueryFn - /* Function that extracts array items from wrapped API responses (e.g metadata, pagination) */ + /** + * Extracts the row array TanStack DB materializes from the Query response. + * The Query cache keeps the original response shape. + */ select?: (data: TQueryData) => Array /** The TanStack Query client instance */ queryClient: QueryClient diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 1dd6e21c8d..9a792093f4 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -898,6 +898,46 @@ describe(`QueryCollection`, () => { expect(initialCache).toEqual(initialMetaData) }) + it(`materializes selected rows while preserving wrapped Query cache response`, async () => { + const queryKey = [`select-row-extraction-test`] + const wrappedResponse = { + items: initialMetaData.data, + meta: { page: 1, total: initialMetaData.data.length }, + } + const expectedCacheResponse = { + items: initialMetaData.data.map((item) => ({ ...item })), + meta: { page: 1, total: initialMetaData.data.length }, + } + + const queryFn = vi.fn().mockResolvedValue(wrappedResponse) + const select = vi.fn((data: typeof wrappedResponse) => data.items) + + const options = queryCollectionOptions({ + id: `select-row-extraction-test`, + queryClient, + queryKey, + queryFn, + select, + getKey, + startSync: true, + }) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(select).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(wrappedResponse.items.length) + }) + + expect(stripVirtualProps(collection.get(`1`))).toEqual( + wrappedResponse.items[0], + ) + expect(stripVirtualProps(collection.get(`2`))).toEqual( + wrappedResponse.items[1], + ) + expect(queryClient.getQueryData(queryKey)).toEqual(expectedCacheResponse) + }) + it(`should not throw error when using writeInsert with select option`, async () => { const queryKey = [`select-writeInsert-test`] const consoleErrorSpy = vi From 580f9d04bc6129353bfee70405d4cb5ce58b72d8 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 9 Jul 2026 09:24:34 -0600 Subject: [PATCH 39/58] docs(query-db-collection): document query option compatibility (#1653) * docs(query-db-collection): document query option compatibility * docs(query-db-collection): clarify option compatibility wording * docs(query-db-collection): add changeset * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../document-query-option-compatibility.md | 5 +++ docs/collections/query-collection.md | 32 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .changeset/document-query-option-compatibility.md diff --git a/.changeset/document-query-option-compatibility.md b/.changeset/document-query-option-compatibility.md new file mode 100644 index 0000000000..d25ea160a1 --- /dev/null +++ b/.changeset/document-query-option-compatibility.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Document the current TanStack Query option compatibility surface for Query Collections, including forwarded options, QueryClient defaults, adapter-owned fields, and common options that are not currently exposed. diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 825087cf65..6c90f53dfb 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -56,13 +56,41 @@ The `queryCollectionOptions` function accepts the following options: ### Query Options +Query Collections use TanStack Query internally, but `queryCollectionOptions` is not a full `QueryObserverOptions` pass-through. It exposes only the Query options supported by the collection adapter. Fields that affect row materialization, collection identity, and synchronization are handled by the adapter itself. + +The following Query options are forwarded to the underlying Query observer: + - `select`: Function that extracts the row array TanStack DB materializes from a wrapped Query response - `enabled`: Whether the query should automatically run (default: `true`) -- `refetchInterval`: Refetch interval in milliseconds (default: 0 — set an interval to enable polling refetching) +- `refetchInterval`: Refetch interval in milliseconds - `retry`: Retry configuration for failed queries - `retryDelay`: Delay between retries - `staleTime`: How long data is considered fresh -- `meta`: Optional metadata that will be passed to the query function context +- `gcTime`: How long unused query data stays in the Query cache +- `meta`: Metadata passed to the query function context. Query Collections may add `loadSubsetOptions` for on-demand queries. + +Except for `meta`, these options are only passed to TanStack Query when you define them. If you omit them, `QueryClient.defaultOptions` can still apply. + +Some fields are owned or reinterpreted by the collection adapter rather than treated as ordinary Query option pass-through: + +- `queryKey`: Identifies the Query cache entry and, in on-demand mode, may be built from load-subset options. +- `queryFn`: Fetches the complete collection state or the requested on-demand subset. +- `select`: Extracts array rows from wrapped responses before they are stored in the collection. This is not the same contract as TanStack Query's `select` option. +- `queryClient`: Supplies the Query client instance used by the collection. +- `syncMode`: Controls whether the collection syncs eagerly or on demand. +- `getKey`: Extracts each row's stable TanStack DB key. +- Mutation handlers such as `onInsert`, `onUpdate`, and `onDelete`. + +Other TanStack Query options are not currently exposed through `queryCollectionOptions`. Common examples include: + +- `initialData` +- `placeholderData` +- `refetchOnWindowFocus` +- `refetchOnReconnect` +- `refetchOnMount` +- `networkMode` +- `throwOnError` +- configurable `structuralSharing` ### Using with `queryOptions(...)` From eabcea743fdfa045a2db01e12bef87403613102a Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 9 Jul 2026 09:33:34 -0600 Subject: [PATCH 40/58] Clarify local write status docs and pending sync contract (#1652) * docs: add write status contract test design * docs: clarify local write status contract * docs: tighten transaction settlement wording * chore: remove planning spec from PR * chore: add changeset * docs: clarify synced write status wording --- .changeset/clarify-local-write-status.md | 5 +++++ packages/db/src/collection/state.ts | 7 ++++++- packages/db/src/transactions.ts | 12 ++++++++++++ packages/db/src/virtual-props.ts | 15 ++++++++++----- packages/db/tests/collection.test.ts | 7 ++++++- 5 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 .changeset/clarify-local-write-status.md diff --git a/.changeset/clarify-local-write-status.md b/.changeset/clarify-local-write-status.md new file mode 100644 index 0000000000..2eb8087e43 --- /dev/null +++ b/.changeset/clarify-local-write-status.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Clarify local write status documentation for `$synced` and `isPersisted.promise`, and add core coverage for queued ambiguous server-key sync while optimistic temp-key inserts are pending. diff --git a/packages/db/src/collection/state.ts b/packages/db/src/collection/state.ts index 5f4449c3b0..0f7b3b868c 100644 --- a/packages/db/src/collection/state.ts +++ b/packages/db/src/collection/state.ts @@ -154,7 +154,12 @@ export class CollectionStateManager< } /** - * Checks if a row has pending optimistic mutations (not yet confirmed by sync). + * Checks whether this row currently has no pending local optimistic writes. + * + * This is local mutation status, not backend confirmation: `true` means the + * row is not currently affected by an optimistic transaction in this + * collection's visible state. + * * Used to compute the $synced virtual property. */ public isRowSynced(key: TKey): boolean { diff --git a/packages/db/src/transactions.ts b/packages/db/src/transactions.ts index 84cbbcfe58..4a3e2f80f2 100644 --- a/packages/db/src/transactions.ts +++ b/packages/db/src/transactions.ts @@ -211,6 +211,18 @@ class Transaction> { public state: TransactionState public mutationFn: MutationFn public mutations: Array> + /** + * Deferred that settles when this transaction settles. + * + * Await `isPersisted.promise`, not `isPersisted` itself. The promise resolves + * when the transaction completes successfully and rejects if the transaction + * fails or is rolled back. + * + * For non-empty commits, the mutation function is the normal settlement + * boundary. This does not inherently prove that a backend has uploaded, + * confirmed, or read back the write unless the mutation function waits for + * that backend observation before returning. + */ public isPersisted: Deferred> public autoCommit: boolean public createdAt: Date diff --git a/packages/db/src/virtual-props.ts b/packages/db/src/virtual-props.ts index 3205d31c2d..ef285821fa 100644 --- a/packages/db/src/virtual-props.ts +++ b/packages/db/src/virtual-props.ts @@ -37,7 +37,7 @@ export type VirtualOrigin = 'local' | 'remote' * // Accessing virtual properties on a row * const user = collection.get('user-1') * if (user.$synced) { - * console.log('Confirmed by backend') + * console.log('No pending local optimistic writes for this row') * } * if (user.$origin === 'local') { * console.log('Created/modified locally') @@ -47,7 +47,7 @@ export type VirtualOrigin = 'local' | 'remote' * @example * ```typescript * // Using virtual properties in queries - * const confirmedOrders = createLiveQueryCollection({ + * const ordersWithoutLocalWrites = createLiveQueryCollection({ * query: (q) => q * .from({ order: orders }) * .where(({ order }) => eq(order.$synced, true)) @@ -58,10 +58,15 @@ export interface VirtualRowProps< TKey extends string | number = string | number, > { /** - * Whether this row reflects confirmed state from the backend. + * Whether this row currently has no pending local optimistic writes. * - * - `true`: Row is confirmed by the backend (no pending optimistic mutations) - * - `false`: Row has pending optimistic mutations that haven't been confirmed + * - `true`: No pending local optimistic mutation currently affects this row + * - `false`: One or more pending local optimistic mutations currently affect this row + * + * This is local mutation status. It does not prove that a backend has uploaded, + * confirmed, or read back the row. If you need backend-confirmed status, keep + * your mutation function pending until that backend observation has happened, + * or expose adapter-specific status. * * For local-only collections (no sync), this is always `true`. * For live query collections, this is passed through from the source collection. diff --git a/packages/db/tests/collection.test.ts b/packages/db/tests/collection.test.ts index c204b89afe..e96994db64 100644 --- a/packages/db/tests/collection.test.ts +++ b/packages/db/tests/collection.test.ts @@ -77,7 +77,7 @@ describe(`Collection`, () => { ).toThrow(DuplicateKeySyncError) }) - it(`removes optimistic insert when sync confirms with a different server-generated key`, async () => { + it(`keeps ambiguous server-key sync queued while a temp-key optimistic insert is pending`, async () => { const options = mockSyncCollectionOptionsNoInitialState<{ id: number text: string @@ -102,6 +102,11 @@ describe(`Collection`, () => { options.utils.commit() // The sync commit is held while the local insert transaction is persisting. + // Without an explicit temp-key -> server-key mapping, core cannot know + // whether key 24 is this optimistic insert's server echo or an unrelated + // row, so it must not expose both rows at the same time. + expect(tx.isPersisted.isPending()).toBe(true) + expect(collection.has(24)).toBe(false) expect(getStateEntries(collection)).toEqual([ [4733, { id: 4733, text: `two` }], ]) From aa41c49f143bc7a628ba53b357a720d6957218a0 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 01:56:48 -0600 Subject: [PATCH 41/58] fix: use patch bump for adapter helpers (#1668) --- .changeset/extract-adapter-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/extract-adapter-helpers.md b/.changeset/extract-adapter-helpers.md index 5f956aa12a..5a838025cb 100644 --- a/.changeset/extract-adapter-helpers.md +++ b/.changeset/extract-adapter-helpers.md @@ -1,5 +1,5 @@ --- -'@tanstack/db': minor +'@tanstack/db': patch '@tanstack/react-db': patch '@tanstack/vue-db': patch '@tanstack/svelte-db': patch From cbd6081a275f1d63fb6274d582eb568ff7d09e51 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 13:35:09 -0600 Subject: [PATCH 42/58] refactor(query-db-collection): extract query ownership helpers (#1664) * refactor(query-db-collection): extract query ownership helpers * chore: add query ownership helpers changeset --- .changeset/extract-query-ownership-helpers.md | 5 + packages/query-db-collection/src/query.ts | 101 ++++++++++-------- 2 files changed, 60 insertions(+), 46 deletions(-) create mode 100644 .changeset/extract-query-ownership-helpers.md diff --git a/.changeset/extract-query-ownership-helpers.md b/.changeset/extract-query-ownership-helpers.md new file mode 100644 index 0000000000..df07298f03 --- /dev/null +++ b/.changeset/extract-query-ownership-helpers.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Extract internal query row ownership helpers to make lifecycle cleanup paths easier to reason about while preserving existing behavior. diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index ea303b8ddb..55470b8c4c 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -697,28 +697,58 @@ export function queryCollectionOptions( // 3. Decrements refcount and GCs rows where count reaches 0 const queryRefCounts = new Map() - // Helper function to add a row to the internal state - const addRow = (rowKey: string | number, hashedQueryKey: string) => { - const rowToQueriesSet = rowToQueries.get(rowKey) || new Set() - rowToQueriesSet.add(hashedQueryKey) - rowToQueries.set(rowKey, rowToQueriesSet) - - const queryToRowsSet = queryToRows.get(hashedQueryKey) || new Set() - queryToRowsSet.add(rowKey) - queryToRows.set(hashedQueryKey, queryToRowsSet) + const addRowOwner = (rowKey: string | number, hashedQueryKey: string) => { + const owners = rowToQueries.get(rowKey) || new Set() + owners.add(hashedQueryKey) + rowToQueries.set(rowKey, owners) + + const ownedRows = + queryToRows.get(hashedQueryKey) || new Set() + ownedRows.add(rowKey) + queryToRows.set(hashedQueryKey, ownedRows) } - // Helper function to remove a row from the internal state - const removeRow = (rowKey: string | number, hashedQuerKey: string) => { - const rowToQueriesSet = rowToQueries.get(rowKey) || new Set() - rowToQueriesSet.delete(hashedQuerKey) - rowToQueries.set(rowKey, rowToQueriesSet) + const addRowOwners = (rowKey: string | number, owners: Set) => { + rowToQueries.set(rowKey, new Set(owners)) + owners.forEach((owner) => { + const ownedRows = queryToRows.get(owner) || new Set() + ownedRows.add(rowKey) + queryToRows.set(owner, ownedRows) + }) + } + + const removeRowOwner = (rowKey: string | number, hashedQueryKey: string) => { + const owners = rowToQueries.get(rowKey) || new Set() + owners.delete(hashedQueryKey) + rowToQueries.set(rowKey, owners) + + const ownedRows = + queryToRows.get(hashedQueryKey) || new Set() + ownedRows.delete(rowKey) + queryToRows.set(hashedQueryKey, ownedRows) + + return owners.size === 0 + } + + const removeQueryOwnership = (hashedQueryKey: string) => { + const nextOwnersByRow = new Map>() + + const rowKeys = + queryToRows.get(hashedQueryKey) ?? new Set() + + rowKeys.forEach((rowKey) => { + const owners = rowToQueries.get(rowKey) + + if (!owners) { + return + } - const queryToRowsSet = queryToRows.get(hashedQuerKey) || new Set() - queryToRowsSet.delete(rowKey) - queryToRows.set(hashedQuerKey, queryToRowsSet) + const nextOwners = new Set(owners) + nextOwners.delete(hashedQueryKey) + nextOwnersByRow.set(rowKey, nextOwners) + }) - return rowToQueriesSet.size === 0 + return nextOwnersByRow } const internalSync: SyncConfig[`sync`] = (params) => { @@ -853,12 +883,7 @@ export function queryCollectionOptions( continue } - rowToQueries.set(rowKey, new Set(owners)) - owners.forEach((owner) => { - const queryToRowsSet = queryToRows.get(owner) || new Set() - queryToRowsSet.add(rowKey) - queryToRows.set(owner, queryToRowsSet) - }) + addRowOwners(rowKey, owners) if (owners.has(hashedQueryKey)) { ownedRows.add(rowKey) @@ -944,12 +969,7 @@ export function queryCollectionOptions( return } - rowToQueries.set(row.key, new Set(ownerSet)) - ownerSet.forEach((owner) => { - const queryToRowsSet = queryToRows.get(owner) || new Set() - queryToRowsSet.add(row.key) - queryToRows.set(owner, queryToRowsSet) - }) + addRowOwners(row.key, ownerSet) if (ownerSet.has(hashedQueryKey)) { baseline.set(row.key, { @@ -975,7 +995,7 @@ export function queryCollectionOptions( baseline.forEach(({ value: oldItem, owners }, rowKey) => { owners.delete(hashedQueryKey) setPersistedOwners(rowKey, owners) - const needToRemove = removeRow(rowKey, hashedQueryKey) + const needToRemove = removeRowOwner(rowKey, hashedQueryKey) if (needToRemove) { rowsToDelete.push(oldItem) } @@ -1321,7 +1341,7 @@ export function queryCollectionOptions( const owners = getPersistedOwners(key) owners.delete(hashedQueryKey) setPersistedOwners(key, owners) - const needToRemove = removeRow(key, hashedQueryKey) + const needToRemove = removeRowOwner(key, hashedQueryKey) if (needToRemove) { write({ type: `delete`, value: oldItem }) } @@ -1336,7 +1356,7 @@ export function queryCollectionOptions( owners.add(hashedQueryKey) setPersistedOwners(key, owners) } - addRow(key, hashedQueryKey) + addRowOwner(key, hashedQueryKey) if (!currentSyncedItems.has(key)) { write({ type: `insert`, value: newItem }) } @@ -1506,21 +1526,10 @@ export function queryCollectionOptions( cancelPersistedRetentionExpiry(hashedQueryKey) retainedQueriesPendingRevalidation.delete(hashedQueryKey) - const rowKeys = queryToRows.get(hashedQueryKey) ?? new Set() - const nextOwnersByRow = new Map>() + const nextOwnersByRow = removeQueryOwnership(hashedQueryKey) const rowsToDelete: Array = [] - rowKeys.forEach((rowKey) => { - const queries = rowToQueries.get(rowKey) - - if (!queries) { - return - } - - const nextOwners = new Set(queries) - nextOwners.delete(hashedQueryKey) - nextOwnersByRow.set(rowKey, nextOwners) - + nextOwnersByRow.forEach((nextOwners, rowKey) => { if (nextOwners.size === 0 && collection.has(rowKey)) { rowsToDelete.push(collection.get(rowKey)) } From d64e1abe812ccebe77f55f396ac560854d68d062 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 13:37:20 -0600 Subject: [PATCH 43/58] feat(query-db-collection): support more top-level query options (#1665) * Apply query options review fixes * ci: apply automated fixes * Use flat query option fields * Mount query client during collection sync --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/query-options-pass-through.md | 5 + docs/collections/query-collection.md | 44 ++++-- packages/query-db-collection/src/query.ts | 126 +++++++++++++++-- .../query-db-collection/tests/query.test-d.ts | 24 ++++ .../query-db-collection/tests/query.test.ts | 131 +++++++++++++++++- 5 files changed, 306 insertions(+), 24 deletions(-) create mode 100644 .changeset/query-options-pass-through.md diff --git a/.changeset/query-options-pass-through.md b/.changeset/query-options-pass-through.md new file mode 100644 index 0000000000..70a48e43ec --- /dev/null +++ b/.changeset/query-options-pass-through.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': minor +--- + +Add top-level Query Collection support for additional Query observer options while preserving QueryClient defaultOptions behavior. diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 6c90f53dfb..0a0265c391 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -56,9 +56,9 @@ The `queryCollectionOptions` function accepts the following options: ### Query Options -Query Collections use TanStack Query internally, but `queryCollectionOptions` is not a full `QueryObserverOptions` pass-through. It exposes only the Query options supported by the collection adapter. Fields that affect row materialization, collection identity, and synchronization are handled by the adapter itself. +Query Collections use TanStack Query internally and expose supported Query observer options as top-level `queryCollectionOptions` fields. -The following Query options are forwarded to the underlying Query observer: +The following top-level Query Collection options are forwarded to the underlying Query observer: - `select`: Function that extracts the row array TanStack DB materializes from a wrapped Query response - `enabled`: Whether the query should automatically run (default: `true`) @@ -67,9 +67,28 @@ The following Query options are forwarded to the underlying Query observer: - `retryDelay`: Delay between retries - `staleTime`: How long data is considered fresh - `gcTime`: How long unused query data stays in the Query cache +- `refetchOnWindowFocus`: Whether to refetch when the window regains focus +- `refetchOnReconnect`: Whether to refetch when the network reconnects +- `refetchOnMount`: Whether to refetch when the observer mounts +- `networkMode`: Query network mode - `meta`: Metadata passed to the query function context. Query Collections may add `loadSubsetOptions` for on-demand queries. -Except for `meta`, these options are only passed to TanStack Query when you define them. If you omit them, `QueryClient.defaultOptions` can still apply. +```ts +const todosCollection = createCollection( + queryCollectionOptions({ + queryKey: ["todos"], + queryFn: fetchTodos, + queryClient, + getKey: (todo) => todo.id, + refetchOnWindowFocus: true, + refetchOnReconnect: true, + refetchOnMount: "always", + networkMode: "online", + }) +) +``` + +Top-level `meta` is always merged by Query Collection so it can add on-demand `loadSubsetOptions`. Other supported top-level Query options are only passed to TanStack Query when you define them. If you omit them, `QueryClient.defaultOptions` can still apply. Some fields are owned or reinterpreted by the collection adapter rather than treated as ordinary Query option pass-through: @@ -81,20 +100,19 @@ Some fields are owned or reinterpreted by the collection adapter rather than tre - `getKey`: Extracts each row's stable TanStack DB key. - Mutation handlers such as `onInsert`, `onUpdate`, and `onDelete`. -Other TanStack Query options are not currently exposed through `queryCollectionOptions`. Common examples include: +Some TanStack Query fields are owned or reinterpreted by Query Collection and are intentionally not exposed as ordinary Query observer options: + +- `queryKey`, `queryFn`, and `queryClient` +- `select` (Query Collection uses this for row extraction, not TanStack Query's observer-level `select` contract) +- `meta` (merged by Query Collection so on-demand `loadSubsetOptions` can be included) +- `subscribed` (Query Collection owns the observer subscription lifecycle) +- `structuralSharing` and `notifyOnChangeProps` (managed by Query Collection synchronization) -- `initialData` -- `placeholderData` -- `refetchOnWindowFocus` -- `refetchOnReconnect` -- `refetchOnMount` -- `networkMode` -- `throwOnError` -- configurable `structuralSharing` +Other semantic options, such as `initialData`, `placeholderData`, and TanStack Query observer-level `select`, are intentionally deferred until their Query Collection behavior is explicitly designed. ### Using with `queryOptions(...)` -If your app already uses TanStack Query's `queryOptions` helper (e.g. from `@tanstack/react-query`), you can spread those options into `queryCollectionOptions`. Note that `queryFn` must be explicitly provided since query collections require it both in types and at runtime: +If your app already uses TanStack Query's `queryOptions` helper (e.g. from `@tanstack/react-query`), you can spread compatible top-level options into `queryCollectionOptions`. Note that `queryFn` must be explicitly provided since query collections require it both in types and at runtime, and Query Collection's `select` option is for row extraction rather than TanStack Query observer-level selection: ```typescript import { QueryClient } from "@tanstack/query-core" diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 55470b8c4c..7b4578d9e3 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -49,6 +49,40 @@ type InferSchemaInput = T extends StandardSchemaV1 type TQueryKeyBuilder = (opts: LoadSubsetOptions) => TQueryKey +const queryObserverOptionKeys = [ + `enabled`, + `refetchInterval`, + `retry`, + `retryDelay`, + `staleTime`, + `gcTime`, + `refetchOnWindowFocus`, + `refetchOnReconnect`, + `refetchOnMount`, + `networkMode`, +] as const + +type QueryObserverOptionKey = (typeof queryObserverOptionKeys)[number] + +type QueryObserverOptionValues = Pick< + QueryObserverOptions, any, Array, Array, any>, + QueryObserverOptionKey +> + +function pickDefinedQueryObserverOptions( + config: Partial, +): Partial { + const options: Partial = {} + + for (const key of queryObserverOptionKeys) { + if (config[key] !== undefined) { + ;(options as Record)[key] = config[key] + } + } + + return options +} + /** * Configuration options for creating a Query Collection * @template T - The explicit type of items stored in the collection @@ -129,6 +163,34 @@ export interface QueryCollectionConfig< TQueryData, TQueryKey >[`gcTime`] + refetchOnWindowFocus?: QueryObserverOptions< + TQueryData, + TError, + Array, + TQueryData, + TQueryKey + >[`refetchOnWindowFocus`] + refetchOnReconnect?: QueryObserverOptions< + TQueryData, + TError, + Array, + TQueryData, + TQueryKey + >[`refetchOnReconnect`] + refetchOnMount?: QueryObserverOptions< + TQueryData, + TError, + Array, + TQueryData, + TQueryKey + >[`refetchOnMount`] + networkMode?: QueryObserverOptions< + TQueryData, + TError, + Array, + TQueryData, + TQueryKey + >[`networkMode`] persistedGcTime?: number /** @@ -596,6 +658,10 @@ export function queryCollectionOptions( retryDelay, staleTime, gcTime, + refetchOnWindowFocus, + refetchOnReconnect, + refetchOnMount, + networkMode, persistedGcTime, getKey, onInsert, @@ -1192,19 +1258,23 @@ export function queryCollectionOptions( Array, any > = { + ...pickDefinedQueryObserverOptions({ + enabled, + refetchInterval, + retry, + retryDelay, + staleTime, + gcTime, + refetchOnWindowFocus, + refetchOnReconnect, + refetchOnMount, + networkMode, + }), queryKey: key, queryFn: queryFunction, meta: extendedMeta, structuralSharing: true, notifyOnChangeProps: `all`, - - // Only include options that are explicitly defined to allow QueryClient defaultOptions to be used - ...(enabled !== undefined && { enabled }), - ...(refetchInterval !== undefined && { refetchInterval }), - ...(retry !== undefined && { retry }), - ...(retryDelay !== undefined && { retryDelay }), - ...(staleTime !== undefined && { staleTime }), - ...(gcTime !== undefined && { gcTime }), } const localObserver = new QueryObserver< @@ -1885,6 +1955,23 @@ export function queryCollectionOptions( // Enhanced internalSync that captures write functions for manual use const enhancedInternalSync: SyncConfig[`sync`] = (params) => { const { begin, write, commit, collection } = params + let queryClientMounted = false + + const mountQueryClient = () => { + if (!queryClientMounted) { + queryClient.mount() + queryClientMounted = true + } + } + + const unmountQueryClient = () => { + if (queryClientMounted) { + queryClient.unmount() + queryClientMounted = false + } + } + + mountQueryClient() // Get the base query key for the context (handle both static and function-based keys) const contextQueryKey = @@ -1904,8 +1991,27 @@ export function queryCollectionOptions( updateCacheData, } - // Call the original internalSync logic - return internalSync(params) + // Call the original internalSync logic, pairing QueryClient mount with the + // collection sync lifecycle so focus/reconnect managers dispatch events for + // standalone QueryClient usage. + const syncResult = internalSync(params) + const sync = + typeof syncResult === `function` + ? { cleanup: syncResult } + : typeof syncResult === `object` + ? syncResult + : {} + + return { + ...sync, + cleanup: () => { + try { + sync.cleanup?.() + } finally { + unmountQueryClient() + } + }, + } } // Create write utils using the manual-sync module diff --git a/packages/query-db-collection/tests/query.test-d.ts b/packages/query-db-collection/tests/query.test-d.ts index c526398f8a..30e8f64c9c 100644 --- a/packages/query-db-collection/tests/query.test-d.ts +++ b/packages/query-db-collection/tests/query.test-d.ts @@ -32,6 +32,30 @@ describe(`Query collection type resolution tests`, () => { // Create a mock QueryClient for tests const queryClient = new QueryClient() + it(`should type supported top-level Query observer options and reject adapter-owned fields`, () => { + queryCollectionOptions({ + id: `query-options-types`, + queryClient, + queryKey: [`query-options-types`], + queryFn: () => Promise.resolve([]), + getKey: (item) => item.id, + refetchOnWindowFocus: true, + refetchOnReconnect: true, + refetchOnMount: `always`, + networkMode: `online`, + }) + + queryCollectionOptions({ + id: `query-options-subscribed-owned`, + queryClient, + queryKey: [`query-options-subscribed-owned`], + queryFn: () => Promise.resolve([]), + getKey: (item) => item.id, + // @ts-expect-error Query Collection owns observer subscription lifecycle. + subscribed: false, + }) + }) + it(`should prioritize explicit type in QueryCollectionConfig`, () => { const options = queryCollectionOptions({ id: `test`, diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 9a792093f4..c711d661f7 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -1,5 +1,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { QueryClient, dehydrate, hashKey } from '@tanstack/query-core' +import { + QueryClient, + dehydrate, + focusManager, + hashKey, + onlineManager, +} from '@tanstack/query-core' import { BTreeIndex, createCollection, @@ -185,6 +191,129 @@ describe(`QueryCollection`, () => { queryClient.clear() }) + it(`should pass through additional top-level Query observer options`, async () => { + const queryKey = [`query-options-pass-through`] + const queryFn = vi.fn().mockResolvedValue([{ id: `1`, name: `Item 1` }]) + + const collection = createCollection( + queryCollectionOptions({ + id: `query-options-pass-through`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnWindowFocus: true, + refetchOnReconnect: true, + refetchOnMount: `always`, + networkMode: `online`, + }), + ) + + await vi.waitFor(() => { + expect(collection.size).toBe(1) + }) + + const query = queryClient.getQueryCache().find({ queryKey, exact: true }) + const options = query?.options as any + expect(options.refetchOnWindowFocus).toBe(true) + expect(options.refetchOnReconnect).toBe(true) + expect(options.refetchOnMount).toBe(`always`) + expect(options.networkMode).toBe(`online`) + }) + + it(`should refetch on focus and reconnect with standalone QueryClient`, async () => { + const queryKey = [`query-options-event-refetch`] + const queryFn = vi + .fn() + .mockResolvedValueOnce([{ id: `1`, name: `Initial` }]) + .mockResolvedValueOnce([{ id: `1`, name: `Focused` }]) + .mockResolvedValueOnce([{ id: `1`, name: `Reconnected` }]) + + const collection = createCollection( + queryCollectionOptions({ + id: `query-options-event-refetch`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + staleTime: 0, + refetchOnWindowFocus: true, + refetchOnReconnect: true, + }), + ) + + try { + await vi.waitFor(() => { + expect(collection.size).toBe(1) + expect(queryFn).toHaveBeenCalledTimes(1) + }) + + focusManager.setFocused(false) + focusManager.setFocused(true) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(2) + }) + + onlineManager.setOnline(false) + onlineManager.setOnline(true) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(3) + }) + } finally { + await collection.cleanup() + focusManager.setFocused(undefined) + onlineManager.setOnline(true) + } + }) + + it(`should omit undefined Query observer options to preserve defaults`, async () => { + const queryKey = [`query-options-default-preservation`] + const queryFn = vi.fn().mockResolvedValue([{ id: `1`, name: `Item 1` }]) + + const clientWithDefaults = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1234, + retry: 3, + refetchOnWindowFocus: false, + }, + }, + }) + + const collection = createCollection( + queryCollectionOptions({ + id: `query-options-default-preservation`, + queryClient: clientWithDefaults, + queryKey, + queryFn, + getKey, + startSync: true, + staleTime: 5678, + retry: undefined, + refetchOnWindowFocus: true, + }), + ) + + await vi.waitFor(() => { + expect(collection.size).toBe(1) + }) + + const query = clientWithDefaults.getQueryCache().find({ + queryKey, + exact: true, + }) + const options = query?.options as any + expect(options.staleTime).toBe(5678) + expect(options.retry).toBe(3) + expect(options.refetchOnWindowFocus).toBe(true) + + clientWithDefaults.clear() + }) + it(`should initialize and fetch initial data`, async () => { const queryKey = [`testItems`] const initialItems: Array = [ From e51d00867942c4c2576672f98f2e79ae65b3998e Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 13:41:33 -0600 Subject: [PATCH 44/58] docs(query-db-collection): document runtime QueryClient factory pattern (#1667) * docs(query-db-collection): document runtime QueryClient factory pattern * Add changeset for runtime QueryClient docs * ci: apply automated fixes * docs(query-db-collection): clarify collection instance lifetime * docs: remove changeset for docs-only update --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- docs/collections/query-collection.md | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 0a0265c391..1c75f022dd 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -54,6 +54,84 @@ The `queryCollectionOptions` function accepts the following options: - `queryClient`: TanStack Query client instance - `getKey`: Function to extract the unique key from an item +### Creating Collection Options from a Runtime QueryClient + +`queryCollectionOptions` needs a `queryClient` when the collection options are created. In SSR, TanStack Start, tests, or multi-tenant apps, that `QueryClient` is often request-local or route-local rather than module-global. + +Keep shared collection configuration in a factory function that accepts the runtime `QueryClient`: + +```typescript +import { QueryClient } from "@tanstack/query-core" +import { createCollection } from "@tanstack/db" +import { queryCollectionOptions } from "@tanstack/query-db-collection" + +interface Todo { + id: string + title: string +} + +export function todoCollectionOptions(queryClient: QueryClient) { + return queryCollectionOptions({ + queryKey: ["todos"], + queryFn: async () => { + const response = await fetch("/api/todos") + return response.json() as Promise> + }, + queryClient, + getKey: (todo) => todo.id, + }) +} + +function createTodosCollection(queryClient: QueryClient) { + return createCollection(todoCollectionOptions(queryClient)) +} +``` + +Create the collection once for each scoped `QueryClient` and parameter set, then reuse that `Collection` instance. Creating multiple collections with the same `QueryClient` and `queryKey` gives each collection its own materialized state, lifecycle, subscriptions, and optimistic mutations. + +In request-scoped environments, store the collection in request or router context. For client-side scopes, memoize by `QueryClient`: + +```typescript +type TodosCollection = ReturnType + +const collectionsByClient = new WeakMap() + +export function getTodosCollection( + queryClient: QueryClient, +): TodosCollection { + let collection = collectionsByClient.get(queryClient) + + if (!collection) { + collection = createTodosCollection(queryClient) + collectionsByClient.set(queryClient, collection) + } + + return collection +} +``` + +Avoid calling `createCollection(todoCollectionOptions(queryClient))` independently during render or in each consumer. Share the stable collection instance for the lifetime of that `QueryClient` scope. + +The same pattern works for scoped or parameterized collections. Pass route params, a tenant ID, or filters into the factory alongside the `QueryClient`: + +```typescript +export function projectTodosCollectionOptions( + queryClient: QueryClient, + projectId: string, +) { + return queryCollectionOptions({ + queryKey: ["projects", projectId, "todos"], + queryFn: () => fetchProjectTodos(projectId), + queryClient, + getKey: (todo) => todo.id, + }) +} +``` + +For parameterized collections, memoize by both the scoped `QueryClient` and the parameter tuple. If a long-lived scope can create unbounded parameter sets, add eviction or dispose collections when they are no longer needed. + +This keeps SSR and request-scoped code from sharing a global `QueryClient` while keeping each collection instance stable within its scope. + ### Query Options Query Collections use TanStack Query internally and expose supported Query observer options as top-level `queryCollectionOptions` fields. From 09d7d677b43b0c5f33f13f0c05bfbb4afa88905b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:48:41 -0600 Subject: [PATCH 45/58] ci: Version Packages (#1661) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/angular-config-object-startsync.md | 7 -- .changeset/clarify-local-write-status.md | 5 -- .changeset/clarify-query-select-semantics.md | 5 -- .../document-query-option-compatibility.md | 5 -- .changeset/extract-adapter-helpers.md | 12 ---- .changeset/query-invalidation-matrix.md | 5 -- .changeset/svelte-disabled-query-fix.md | 7 -- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 15 ++++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 7 ++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 10 +++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 13 ++++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 11 +++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 11 +++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 15 ++++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 11 +++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 65 files changed, 300 insertions(+), 138 deletions(-) delete mode 100644 .changeset/angular-config-object-startsync.md delete mode 100644 .changeset/clarify-local-write-status.md delete mode 100644 .changeset/clarify-query-select-semantics.md delete mode 100644 .changeset/document-query-option-compatibility.md delete mode 100644 .changeset/extract-adapter-helpers.md delete mode 100644 .changeset/query-invalidation-matrix.md delete mode 100644 .changeset/svelte-disabled-query-fix.md diff --git a/.changeset/angular-config-object-startsync.md b/.changeset/angular-config-object-startsync.md deleted file mode 100644 index 20c2486093..0000000000 --- a/.changeset/angular-config-object-startsync.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/angular-db': patch ---- - -fix(angular-db): a `{ query }` config-object passed to `injectLiveQuery` now syncs - -The config-object branch called `createLiveQueryCollection(opts)` without defaulting `startSync`, unlike the query-function and reactive-options branches (which force `startSync: true`), so a bare `{ query }` never started syncing and produced no data. It now defaults `startSync: true` and `gcTime: 0` while still honoring any explicit values in the config. diff --git a/.changeset/clarify-local-write-status.md b/.changeset/clarify-local-write-status.md deleted file mode 100644 index 2eb8087e43..0000000000 --- a/.changeset/clarify-local-write-status.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Clarify local write status documentation for `$synced` and `isPersisted.promise`, and add core coverage for queued ambiguous server-key sync while optimistic temp-key inserts are pending. diff --git a/.changeset/clarify-query-select-semantics.md b/.changeset/clarify-query-select-semantics.md deleted file mode 100644 index 632c15c281..0000000000 --- a/.changeset/clarify-query-select-semantics.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/query-db-collection': patch ---- - -Clarify that `select` extracts rows for DB materialization while preserving the wrapped TanStack Query cache response. diff --git a/.changeset/document-query-option-compatibility.md b/.changeset/document-query-option-compatibility.md deleted file mode 100644 index d25ea160a1..0000000000 --- a/.changeset/document-query-option-compatibility.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/query-db-collection': patch ---- - -Document the current TanStack Query option compatibility surface for Query Collections, including forwarded options, QueryClient defaults, adapter-owned fields, and common options that are not currently exposed. diff --git a/.changeset/extract-adapter-helpers.md b/.changeset/extract-adapter-helpers.md deleted file mode 100644 index 5a838025cb..0000000000 --- a/.changeset/extract-adapter-helpers.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@tanstack/db': patch -'@tanstack/react-db': patch -'@tanstack/vue-db': patch -'@tanstack/svelte-db': patch -'@tanstack/solid-db': patch -'@tanstack/angular-db': patch ---- - -Extract shared live-query adapter helpers into `@tanstack/db` - -Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. diff --git a/.changeset/query-invalidation-matrix.md b/.changeset/query-invalidation-matrix.md deleted file mode 100644 index 9737b7e0ec..0000000000 --- a/.changeset/query-invalidation-matrix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/query-db-collection': patch ---- - -Add coverage for query invalidation behavior across eager and on-demand query collections. diff --git a/.changeset/svelte-disabled-query-fix.md b/.changeset/svelte-disabled-query-fix.md deleted file mode 100644 index 1b4892d8db..0000000000 --- a/.changeset/svelte-disabled-query-fix.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/svelte-db': patch ---- - -fix(svelte-db): a disabled `useLiveQuery` (query callback returning `null`/`undefined`) no longer crashes - -The reactive-getter unwrapping (`toValue`) called the query callback and, when it returned `null`/`undefined` to signal a disabled query, passed the unwrapped `null` into `createLiveQueryCollection`, throwing in `getQueryIR`. A `null`/`undefined` resolved value is now treated as a disabled query (returns the `disabled` state) as it is in the other adapters. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index 36f8d79a61..8dffb3be46 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.74", - "@tanstack/db": "^0.6.14", + "@tanstack/angular-db": "^0.1.75", + "@tanstack/db": "^0.6.15", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index dc4462179c..63de4d9ab9 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.18", - "@tanstack/node-db-sqlite-persistence": "^0.2.6", - "@tanstack/offline-transactions": "^1.0.39", - "@tanstack/query-db-collection": "^1.0.47", - "@tanstack/react-db": "^0.1.92", + "@tanstack/electron-db-sqlite-persistence": "^0.1.19", + "@tanstack/node-db-sqlite-persistence": "^0.2.7", + "@tanstack/offline-transactions": "^1.0.40", + "@tanstack/query-db-collection": "^1.0.48", + "@tanstack/react-db": "^0.1.93", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 75bee00f5b..246d598225 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.14", - "@tanstack/offline-transactions": "^1.0.39", - "@tanstack/query-db-collection": "^1.0.47", - "@tanstack/react-db": "^0.1.92", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.6", + "@tanstack/db": "^0.6.15", + "@tanstack/offline-transactions": "^1.0.40", + "@tanstack/query-db-collection": "^1.0.48", + "@tanstack/react-db": "^0.1.93", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.7", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 513cd4d333..455015aaba 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.14", - "@tanstack/electric-db-collection": "^0.3.12", - "@tanstack/offline-transactions": "^1.0.39", - "@tanstack/react-db": "^0.1.92", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.6", + "@tanstack/db": "^0.6.15", + "@tanstack/electric-db-collection": "^0.3.13", + "@tanstack/offline-transactions": "^1.0.40", + "@tanstack/react-db": "^0.1.93", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.7", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index 0b3f3585f3..68f0819bd4 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.6", - "@tanstack/db": "^0.6.14", - "@tanstack/offline-transactions": "^1.0.39", - "@tanstack/query-db-collection": "^1.0.47", - "@tanstack/react-db": "^0.1.92", + "@tanstack/browser-db-sqlite-persistence": "^0.2.7", + "@tanstack/db": "^0.6.15", + "@tanstack/offline-transactions": "^1.0.40", + "@tanstack/query-db-collection": "^1.0.48", + "@tanstack/react-db": "^0.1.93", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 2485cc9206..33e86546a4 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.14", - "@tanstack/react-db": "^0.1.92", + "@tanstack/db": "^0.6.15", + "@tanstack/react-db": "^0.1.93", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 31d23e70ca..168de27bdc 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.47", - "@tanstack/react-db": "^0.1.92", + "@tanstack/query-db-collection": "^1.0.48", + "@tanstack/react-db": "^0.1.93", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index bdae873af2..b288d50bf7 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.12", + "@tanstack/electric-db-collection": "^0.3.13", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.47", - "@tanstack/react-db": "^0.1.92", + "@tanstack/query-db-collection": "^1.0.48", + "@tanstack/react-db": "^0.1.93", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.92", + "@tanstack/trailbase-db-collection": "^0.1.93", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index b77eccea9a..09c6177e74 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.12", + "@tanstack/electric-db-collection": "^0.3.13", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.47", - "@tanstack/solid-db": "^0.2.28", + "@tanstack/query-db-collection": "^1.0.48", + "@tanstack/solid-db": "^0.2.29", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.92", + "@tanstack/trailbase-db-collection": "^0.1.93", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 07273f51e4..dec7533dc7 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,20 @@ # @tanstack/angular-db +## 0.1.75 + +### Patch Changes + +- fix(angular-db): a `{ query }` config-object passed to `injectLiveQuery` now syncs ([#1638](https://github.com/TanStack/db/pull/1638)) + + The config-object branch called `createLiveQueryCollection(opts)` without defaulting `startSync`, unlike the query-function and reactive-options branches (which force `startSync: true`), so a bare `{ query }` never started syncing and produced no data. It now defaults `startSync: true` and `gcTime: 0` while still honoring any explicit values in the config. + +- Extract shared live-query adapter helpers into `@tanstack/db` ([#1641](https://github.com/TanStack/db/pull/1641)) + + Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.1.74 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 33ef8684b6..36a4202673 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.74", + "version": "0.1.75", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index e7ae0b48f9..7d2b10e469 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.7 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.2.6 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 2a984e17c1..4c8a2e0f7c 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.6", + "version": "0.2.7", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 705d509c9e..1e0f638c88 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.7 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.2.6 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index 04ca78e471..7656bbae25 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.19 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + - @tanstack/capacitor-db-sqlite-persistence@0.2.7 + ## 0.0.18 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index ce75ad42ad..30acd2d6c4 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.18", + "version": "0.0.19", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 08af68894f..c3d2d976ad 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.6", + "version": "0.2.7", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index bfdba6a502..776cf0c26b 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.7 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.2.6 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index a70de7a1c4..1c96a630b1 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.6", + "version": "0.2.7", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index f7bb4e1142..09d0edaea3 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.7 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.2.6 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 3d2762a41c..9756a942aa 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.6", + "version": "0.2.7", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index a6e264f54e..556df7285e 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,15 @@ # @tanstack/db +## 0.6.15 + +### Patch Changes + +- Clarify local write status documentation for `$synced` and `isPersisted.promise`, and add core coverage for queued ambiguous server-key sync while optimistic temp-key inserts are pending. ([#1652](https://github.com/TanStack/db/pull/1652)) + +- Extract shared live-query adapter helpers into `@tanstack/db` ([#1641](https://github.com/TanStack/db/pull/1641)) + + Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. + ## 0.6.14 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 3964473ad3..6e94d9a709 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.14", + "version": "0.6.15", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 573abc3c0c..c9677ebb0b 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.13 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.3.12 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index 6a0e2677d2..c42d885abc 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.12", + "version": "0.3.13", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index d727a23d17..9aa150d58f 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.19 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.1.18 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 586e2b8a41..bbc138691a 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.18", + "version": "0.1.19", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index 5af1edf510..f5c7934423 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.7 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.2.6 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index b40a888144..eb4ef71ec5 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.19 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + - @tanstack/expo-db-sqlite-persistence@0.2.7 + ## 0.0.18 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index e1b0e245fb..28f2228309 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.18", + "version": "0.0.19", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 324dff95ac..4c8590527a 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.6", + "version": "0.2.7", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index 5145428dbb..198b3d891a 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.7 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.2.6 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index 6df0e4a2b8..ab9cf69355 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.6", + "version": "0.2.7", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index c1c47150f3..8ccd643a78 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.40 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 1.0.39 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index 124b787c08..da694381ad 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.39", + "version": "1.0.40", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index 02d39c72ae..24ca9d7d57 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.53 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.1.52 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index fd3978c83e..3904572bd5 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.52", + "version": "0.1.53", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index 6c5f66ae7d..4b1ddff45c 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,18 @@ # @tanstack/query-db-collection +## 1.0.48 + +### Patch Changes + +- Clarify that `select` extracts rows for DB materialization while preserving the wrapped TanStack Query cache response. ([#1654](https://github.com/TanStack/db/pull/1654)) + +- Document the current TanStack Query option compatibility surface for Query Collections, including forwarded options, QueryClient defaults, adapter-owned fields, and common options that are not currently exposed. ([#1653](https://github.com/TanStack/db/pull/1653)) + +- Add coverage for query invalidation behavior across eager and on-demand query collections. ([#1655](https://github.com/TanStack/db/pull/1655)) + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 1.0.47 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index c0cb7df5aa..1b710c17d9 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.47", + "version": "1.0.48", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index a7f09f0039..82a036946e 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,16 @@ # @tanstack/react-db +## 0.1.93 + +### Patch Changes + +- Extract shared live-query adapter helpers into `@tanstack/db` ([#1641](https://github.com/TanStack/db/pull/1641)) + + Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.1.92 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 175e5a7099..d39f14ef10 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.92", + "version": "0.1.93", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 6f2c4f0098..bd94edb877 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.7 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.2.6 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index cd46124d54..4165791e16 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.6", + "version": "0.2.7", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 88a2b5bb30..96660dac89 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.81 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.1.80 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index fa341f2fbe..617a3964b2 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.80", + "version": "0.1.81", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 9f2d588391..148a36a6f8 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,16 @@ # @tanstack/react-db +## 0.2.29 + +### Patch Changes + +- Extract shared live-query adapter helpers into `@tanstack/db` ([#1641](https://github.com/TanStack/db/pull/1641)) + + Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.2.28 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index b025bbee6e..c96fb373c7 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.28", + "version": "0.2.29", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 7505d18cb2..61b14dffc0 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,20 @@ # @tanstack/svelte-db +## 0.1.92 + +### Patch Changes + +- Extract shared live-query adapter helpers into `@tanstack/db` ([#1641](https://github.com/TanStack/db/pull/1641)) + + Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. + +- fix(svelte-db): a disabled `useLiveQuery` (query callback returning `null`/`undefined`) no longer crashes ([#1637](https://github.com/TanStack/db/pull/1637)) + + The reactive-getter unwrapping (`toValue`) called the query callback and, when it returned `null`/`undefined` to signal a disabled query, passed the unwrapped `null` into `createLiveQueryCollection`, throwing in `getQueryIR`. A `null`/`undefined` resolved value is now treated as a disabled query (returns the `disabled` state) as it is in the other adapters. + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.1.91 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index e340f539a3..18e4f6eb19 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.91", + "version": "0.1.92", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index d01531deb4..37e9f05253 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.7 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.7 + ## 0.2.6 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 269a6acf17..6e6389e7c0 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.19 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + - @tanstack/tauri-db-sqlite-persistence@0.2.7 + ## 0.0.18 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 34d33ecd72..c27eb64f56 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.18", + "version": "0.0.19", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index 9111062bfb..f741e9a966 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.6", + "version": "0.2.7", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index 5616e1757f..16643dbb0d 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.93 + +### Patch Changes + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.1.92 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 1a2dc200c4..9435da8932 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.92", + "version": "0.1.93", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 00024f6395..4331b7ba2c 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,16 @@ # @tanstack/vue-db +## 0.0.126 + +### Patch Changes + +- Extract shared live-query adapter helpers into `@tanstack/db` ([#1641](https://github.com/TanStack/db/pull/1641)) + + Adds `isCollection`, `isSingleResultCollection`, and `getLiveQueryStatusFlags` to `@tanstack/db` and migrates all five framework adapters to use them. `isCollection` replaces the per-adapter duck-typing and Solid's `instanceof CollectionImpl` with one structural, multi-realm-safe guard (the `instanceof` form gave false negatives across dual-package boundaries). No behavior change; internal deduplication only. + +- Updated dependencies [[`eabcea7`](https://github.com/TanStack/db/commit/eabcea743fdfa045a2db01e12bef87403613102a), [`6d4c096`](https://github.com/TanStack/db/commit/6d4c096395b7ff3f428122ea8842bbead551a8c9)]: + - @tanstack/db@0.6.15 + ## 0.0.125 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index ff4f7771dd..4a53013316 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.125", + "version": "0.0.126", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e230a7f55..9163601ea1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.74 + specifier: ^0.1.75 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.14 + specifier: ^0.6.15 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.18 + specifier: ^0.1.19 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.6 + specifier: ^0.2.7 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.39 + specifier: ^1.0.40 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.47 + specifier: ^1.0.48 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.14 + specifier: ^0.6.15 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.39 + specifier: ^1.0.40 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.47 + specifier: ^1.0.48 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.6 + specifier: ^0.2.7 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.14 + specifier: ^0.6.15 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.12 + specifier: ^0.3.13 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.39 + specifier: ^1.0.40 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.6 + specifier: ^0.2.7 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.6 + specifier: ^0.2.7 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.14 + specifier: ^0.6.15 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.39 + specifier: ^1.0.40 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.47 + specifier: ^1.0.48 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.14 + specifier: ^0.6.15 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.47 + specifier: ^1.0.48 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.12 + specifier: ^0.3.13 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.47 + specifier: ^1.0.48 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.12 + specifier: ^0.3.13 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.47 + specifier: ^1.0.48 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.28 + specifier: ^0.2.29 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.92 + specifier: ^0.1.93 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 6ed8b1555357604f7110522579415ab2c2b25f60 Mon Sep 17 00:00:00 2001 From: Pavel Kudinov Date: Mon, 13 Jul 2026 14:55:19 -0700 Subject: [PATCH 46/58] fix(electric-db-collection): preserve persisted rows on progressive resume (#1493) * fix electric progressive persisted resume * test(electric): cover changes during persisted resume --------- Co-authored-by: Kyle Mathews --- .changeset/progressive-persisted-resume.md | 5 + .../electric-db-collection/src/electric.ts | 6 +- .../tests/electric.test.ts | 125 ++++++++++++++++-- 3 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 .changeset/progressive-persisted-resume.md diff --git a/.changeset/progressive-persisted-resume.md b/.changeset/progressive-persisted-resume.md new file mode 100644 index 0000000000..d23766fa81 --- /dev/null +++ b/.changeset/progressive-persisted-resume.md @@ -0,0 +1,5 @@ +--- +'@tanstack/electric-db-collection': patch +--- + +Prevent progressive Electric collections from truncating persisted rows when resuming from saved Electric shape metadata. diff --git a/packages/electric-db-collection/src/electric.ts b/packages/electric-db-collection/src/electric.ts index 72a9a072e0..22d222c155 100644 --- a/packages/electric-db-collection/src/electric.ts +++ b/packages/electric-db-collection/src/electric.ts @@ -1441,7 +1441,11 @@ function createElectricSync>( let transactionStarted = false const newTxids = new Set() const newSnapshots: Array = [] - let hasReceivedUpToDate = false // Track if we've completed initial sync in progressive mode + // Track if we've completed initial sync in progressive mode. A persisted + // resume starts from an already-committed stream offset, so the next + // up-to-date message must not run the initial atomic swap again. + let hasReceivedUpToDate = + syncMode === `progressive` && canUsePersistedResume // Progressive mode state // Helper to determine if we're buffering the initial sync diff --git a/packages/electric-db-collection/tests/electric.test.ts b/packages/electric-db-collection/tests/electric.test.ts index 868c085213..8eabf0a15d 100644 --- a/packages/electric-db-collection/tests/electric.test.ts +++ b/packages/electric-db-collection/tests/electric.test.ts @@ -95,16 +95,22 @@ describe(`Electric Integration`, () => { const createPersistedAdapter = ( collectionMetadata?: Map, + rows: Map = new Map(), ) => ({ - loadSubset: async () => [], - loadCollectionMetadata: async () => - Array.from((collectionMetadata ?? new Map()).entries()).map( - ([key, value]) => ({ - key, - value, - }), + loadSubset: () => + Promise.resolve( + Array.from(rows.entries()).map(([key, value]) => ({ key, value })), + ), + loadCollectionMetadata: () => + Promise.resolve( + Array.from((collectionMetadata ?? new Map()).entries()).map( + ([key, value]) => ({ + key, + value, + }), + ), ), - applyCommittedTx: async (_collectionId: string, tx: any) => { + applyCommittedTx: (_collectionId: string, tx: any) => { for (const mutation of tx.collectionMetadataMutations ?? []) { if (mutation.type === `delete`) { collectionMetadata?.delete(mutation.key) @@ -112,8 +118,19 @@ describe(`Electric Integration`, () => { collectionMetadata?.set(mutation.key, mutation.value) } } + if (tx.truncate) { + rows.clear() + } + for (const mutation of tx.mutations ?? []) { + if (mutation.type === `delete`) { + rows.delete(mutation.key) + } else { + rows.set(mutation.key, mutation.value) + } + } + return Promise.resolve() }, - ensureIndex: async () => {}, + ensureIndex: () => Promise.resolve(), }) beforeEach(() => { @@ -3179,6 +3196,96 @@ describe(`Electric Integration`, () => { ) }) + it(`should preserve hydrated rows and resumed changes received before up-to-date`, async () => { + vi.clearAllMocks() + + const { ShapeStream } = await import(`@electric-sql/client`) + mockFetchSnapshot.mockResolvedValue({ + metadata: {}, + data: [], + }) + + const collectionMetadata = new Map([ + [ + `electric:resume`, + { + kind: `resume`, + offset: `10_0`, + handle: `handle-1`, + shapeId: `{"params":{"table":"test_table"},"url":"http://test-url"}`, + updatedAt: 1, + }, + ], + ]) + const persistedRows = new Map([ + [1, { id: 1, name: `Persisted User` }], + ]) + + const persistedCollection = createCollection( + persistedCollectionOptions({ + ...(electricCollectionOptions({ + id: `persisted-progressive-resume-test`, + shapeOptions: { + url: `http://test-url`, + params: { + table: `test_table`, + }, + }, + syncMode: `progressive` as const, + getKey: (item: Row) => item.id as number, + startSync: true, + }) as any), + persistence: { + adapter: createPersistedAdapter(collectionMetadata, persistedRows), + }, + }) as any, + ) + + persistedCollection.startSyncImmediate() + await persistedCollection._sync.loadSubset({ limit: 10 }) + + expect(ShapeStream).toHaveBeenCalledWith( + expect.objectContaining({ + offset: `10_0`, + handle: `handle-1`, + }), + ) + expect(stripVirtualProps(persistedCollection.get(1))).toEqual({ + id: 1, + name: `Persisted User`, + }) + + subscriber([ + { + key: `2`, + value: { id: 2, name: `Resumed User` }, + headers: { operation: `insert` }, + }, + ]) + subscriber([ + { + headers: { control: `up-to-date` }, + }, + ]) + + expect( + [persistedCollection.get(1), persistedCollection.get(2)].map( + stripVirtualProps, + ), + ).toEqual([ + { id: 1, name: `Persisted User` }, + { id: 2, name: `Resumed User` }, + ]) + await vi.waitFor(() => { + expect( + [persistedRows.get(1), persistedRows.get(2)].map(stripVirtualProps), + ).toEqual([ + { id: 1, name: `Persisted User` }, + { id: 2, name: `Resumed User` }, + ]) + }) + }) + it(`should not mix explicit handle with persisted offset`, async () => { vi.clearAllMocks() From 286964d72612b59e3e427baabd9870f5a71a4281 Mon Sep 17 00:00:00 2001 From: spokodev Date: Mon, 13 Jul 2026 23:11:43 +0100 Subject: [PATCH 47/58] fix(db): preserve explicit gcTime of 0 on live query collections (#1660) The live query config builder used `this.config.gcTime || 5000`, which treats an explicit `gcTime: 0` (disable GC) as unset and replaces it with the 5s default. The collection is then garbage collected instead of being kept alive. Use `??` so only `undefined` falls back to the default. Co-authored-by: Kyle Mathews --- .changeset/live-query-gctime-zero.md | 5 +++++ .../src/query/live/collection-config-builder.ts | 2 +- .../db/tests/query/live-query-collection.test.ts | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .changeset/live-query-gctime-zero.md diff --git a/.changeset/live-query-gctime-zero.md b/.changeset/live-query-gctime-zero.md new file mode 100644 index 0000000000..ac199c4e55 --- /dev/null +++ b/.changeset/live-query-gctime-zero.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Preserve an explicit `gcTime: 0` on live query collections. The live query config builder used `this.config.gcTime || 5000`, so a `gcTime` of `0` (which disables garbage collection) was treated as unset and silently replaced by the 5s default, causing the collection to be garbage collected instead of kept alive. Use `??` so only `undefined` falls back to the default. diff --git a/packages/db/src/query/live/collection-config-builder.ts b/packages/db/src/query/live/collection-config-builder.ts index a6a51b4788..4d80b3fe17 100644 --- a/packages/db/src/query/live/collection-config-builder.ts +++ b/packages/db/src/query/live/collection-config-builder.ts @@ -248,7 +248,7 @@ export class CollectionConfigBuilder< sync: this.getSyncConfig(), compare: this.compare, defaultStringCollation: this.compareOptions, - gcTime: this.config.gcTime || 5000, // 5 seconds by default for live queries + gcTime: this.config.gcTime ?? 5000, // 5 seconds by default for live queries schema: this.config.schema, onInsert: this.config.onInsert, onUpdate: this.config.onUpdate, diff --git a/packages/db/tests/query/live-query-collection.test.ts b/packages/db/tests/query/live-query-collection.test.ts index 29d10cc27d..a2613c0104 100644 --- a/packages/db/tests/query/live-query-collection.test.ts +++ b/packages/db/tests/query/live-query-collection.test.ts @@ -449,6 +449,21 @@ describe(`createLiveQueryCollection`, () => { }) }) + it(`should forward an explicit gcTime of 0 (disable GC) instead of coercing it to the default`, () => { + const options = liveQueryCollectionOptions({ + query: (q) => + q + .from({ user: usersCollection }) + .where(({ user }) => eq(user.active, true)), + gcTime: 0, + }) + + // gcTime: 0 disables garbage collection. A `|| 5000` fallback treats the + // explicit 0 as unset and silently replaces it with the 5s default, so the + // collection is garbage collected instead of being kept alive. + expect(options.gcTime).toBe(0) + }) + it(`should not reuse finalized graph after GC cleanup (resubscribe is safe)`, async () => { const liveQuery = createLiveQueryCollection({ query: (q) => From 73e5b67e1787355dc7b38070df4d5be234fe4fab Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 16 Jul 2026 14:03:37 -0600 Subject: [PATCH 48/58] test(query-db-collection): characterize cancellation and subset cleanup (#1673) * test: characterize query cancellation cleanup * test: cover query cleanup review findings * Apply query readiness simplification * Add query cleanup changeset --- .changeset/characterize-query-cleanup.md | 5 + packages/query-db-collection/src/query.ts | 70 +++--- .../query-db-collection/tests/query.test.ts | 223 ++++++++++++++++++ 3 files changed, 270 insertions(+), 28 deletions(-) create mode 100644 .changeset/characterize-query-cleanup.md diff --git a/.changeset/characterize-query-cleanup.md b/.changeset/characterize-query-cleanup.md new file mode 100644 index 0000000000..9f41155f97 --- /dev/null +++ b/.changeset/characterize-query-cleanup.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Fix temporary query readiness listeners so subset unload and collection cleanup release them correctly during in-flight requests. diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 7b4578d9e3..f104cba3b1 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -745,6 +745,7 @@ export function queryCollectionOptions( // queryKey → QueryObserver's unsubscribe function const unsubscribes = new Map void>() + const pendingReadyUnsubscribes = new Map void>>() // queryKey → reference count (how many loadSubset calls are active) // Reference counting for QueryObserver lifecycle management @@ -1174,6 +1175,36 @@ export function queryCollectionOptions( } }) + const waitForQueryReady = ( + observer: QueryObserver, any, Array, Array, any>, + hashedQueryKey: string, + ): Promise => + new Promise((resolve, reject) => { + const unsubscribe = observer.subscribe((result) => { + // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized + queueMicrotask(() => { + if (result.isSuccess || result.isError) { + unsubscribe() + const pending = pendingReadyUnsubscribes.get(hashedQueryKey) + pending?.delete(unsubscribe) + if (pending?.size === 0) { + pendingReadyUnsubscribes.delete(hashedQueryKey) + } + + if (result.isSuccess) { + resolve() + } else { + reject(result.error) + } + } + }) + }) + const pending = + pendingReadyUnsubscribes.get(hashedQueryKey) ?? new Set() + pending.add(unsubscribe) + pendingReadyUnsubscribes.set(hashedQueryKey, pending) + }) + const createQueryFromOpts = ( opts: LoadSubsetOptions = {}, queryFunction: typeof queryFn = queryFn, @@ -1234,20 +1265,7 @@ export function queryCollectionOptions( } // Query is still loading, wait for the first result - return new Promise((resolve, reject) => { - const unsubscribe = observer.subscribe((result) => { - // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized - queueMicrotask(() => { - if (result.isSuccess) { - unsubscribe() - resolve() - } else if (result.isError) { - unsubscribe() - reject(result.error) - } - }) - }) - }) + return waitForQueryReady(observer, hashedQueryKey) } } @@ -1317,20 +1335,7 @@ export function queryCollectionOptions( } // Create a promise that resolves when the query result is first available - const readyPromise = new Promise((resolve, reject) => { - const unsubscribe = localObserver.subscribe((result) => { - // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized - queueMicrotask(() => { - if (result.isSuccess) { - unsubscribe() - resolve() - } else if (result.isError) { - unsubscribe() - reject(result.error) - } - }) - }) - }) + const readyPromise = waitForQueryReady(localObserver, hashedQueryKey) // If sync has started or there are subscribers to the collection, subscribe to the query straight away // This creates the main subscription that handles data updates @@ -1590,9 +1595,17 @@ export function queryCollectionOptions( * Perform row-level cleanup and remove all tracking for a query. * Callers are responsible for ensuring the query is safe to cleanup. */ + const unsubscribePendingReadyListeners = (hashedQueryKey: string) => { + pendingReadyUnsubscribes.get(hashedQueryKey)?.forEach((unsubscribe) => { + unsubscribe() + }) + pendingReadyUnsubscribes.delete(hashedQueryKey) + } + const cleanupQueryInternal = (hashedQueryKey: string) => { unsubscribes.get(hashedQueryKey)?.() unsubscribes.delete(hashedQueryKey) + unsubscribePendingReadyListeners(hashedQueryKey) cancelPersistedRetentionExpiry(hashedQueryKey) retainedQueriesPendingRevalidation.delete(hashedQueryKey) @@ -1655,6 +1668,7 @@ export function queryCollectionOptions( // Drop our subscription so hasListeners reflects only active consumers unsubscribes.get(hashedQueryKey)?.() unsubscribes.delete(hashedQueryKey) + unsubscribePendingReadyListeners(hashedQueryKey) } const hasListeners = observer?.hasListeners() ?? false diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index c711d661f7..d72a7a3dac 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -50,6 +50,16 @@ const getKey = (item: TestItem) => item.id // Helper to advance timers and allow microtasks to flush const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0)) +function createDeferred() { + let resolve!: (value: T) => void + let reject!: (reason?: unknown) => void + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise + reject = rejectPromise + }) + return { promise, resolve, reject } +} + function createInMemorySyncMetadataApi< TKey extends string | number = string | number, TItem extends object = Record, @@ -2372,6 +2382,219 @@ describe(`QueryCollection`, () => { expect(collection.size).toBe(0) }) + describe(`query cancellation and subset cleanup lifecycle`, () => { + const createSubset = (collection: Collection) => + createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => eq(item.id, `1`)) + .select(({ item }) => ({ id: item.id, name: item.name })), + }) + + it(`forwards Query Core's signal and aborts an in-flight eager query on collection cleanup`, async () => { + const deferred = createDeferred>() + let signal: AbortSignal | undefined + const collection = createCollection( + queryCollectionOptions({ + id: `signal-forwarding-cleanup-test`, + queryClient, + queryKey: [`signal-forwarding-cleanup-test`], + queryFn: (context) => { + signal = context.signal + // Reading the signal makes Query Core treat the request as cancellable. + void context.signal.aborted + return deferred.promise + }, + getKey, + startSync: true, + }), + ) + + await vi.waitFor(() => expect(signal).toBeDefined()) + expect(signal?.aborted).toBe(false) + + await collection.cleanup() + + expect(signal?.aborted).toBe(true) + expect(collection.size).toBe(0) + // Query Core may retain the cancelled cache entry, but cleanup releases every observer. + expect( + queryClient + .getQueryCache() + .find({ queryKey: [`signal-forwarding-cleanup-test`] }) + ?.getObserversCount() ?? 0, + ).toBe(0) + }) + + it(`cleans listeners immediately and resolves the unloaded preload before its request settles`, async () => { + const deferred = createDeferred>() + const collection = createCollection( + queryCollectionOptions({ + id: `late-subset-result-test`, + queryClient, + queryKey: [`late-subset-result-test`], + queryFn: () => deferred.promise, + getKey, + syncMode: `on-demand`, + }), + ) + const liveQuery = createSubset(collection) + let preloadResolved = false + void liveQuery.preload().then(() => { + preloadResolved = true + }) + + await vi.waitFor(() => expect(queryClient.isFetching()).toBe(1)) + await liveQuery.cleanup() + + const subsetQuery = queryClient.getQueryCache().findAll({ + queryKey: [`late-subset-result-test`], + })[0] + // This assertion runs while the request is unresolved and directly guards the + // ready-listener bookkeeping bug: unload must synchronously detach its observer. + expect(subsetQuery?.getObserversCount() ?? 0).toBe(0) + // Live-query cleanup resolves its preload even though Query Core is still fetching. + expect(preloadResolved).toBe(true) + + deferred.resolve([{ id: `1`, name: `Late item` }]) + await vi.waitFor(() => expect(queryClient.isFetching()).toBe(0)) + + expect(collection.size).toBe(0) + expect(preloadResolved).toBe(true) + expect(subsetQuery?.getObserversCount() ?? 0).toBe(0) + await collection.cleanup() + }) + + it(`preserves an active subset across cache removal and accepts a late notification from its detached observer`, async () => { + const queryKey = [`cache-removal-late-notification-test`] + const collection = createCollection( + queryCollectionOptions({ + id: `cache-removal-late-notification-test`, + queryClient, + queryKey, + queryFn: () => Promise.resolve([{ id: `1`, name: `Initial item` }]), + getKey, + syncMode: `on-demand`, + }), + ) + const liveQuery = createSubset(collection) + await liveQuery.preload() + const subsetQuery = queryClient.getQueryCache().findAll({ queryKey })[0] + expect(subsetQuery).toBeDefined() + + // A Query Core `removed` event can arrive before this collection's observer + // is detached. Existing semantics retain the active rows and observer. + queryClient.getQueryCache().remove(subsetQuery!) + expect(queryClient.getQueryCache().findAll({ queryKey })).toHaveLength( + 0, + ) + expect(collection.get(`1`)?.name).toBe(`Initial item`) + expect(subsetQuery!.getObserversCount()).toBe(1) + + // The retained observer can still notify after its query left the cache. + subsetQuery!.setData([{ id: `1`, name: `Late notification` }]) + await vi.waitFor(() => + expect(collection.get(`1`)?.name).toBe(`Late notification`), + ) + + await liveQuery.cleanup() + expect(subsetQuery!.getObserversCount()).toBe(0) + expect(collection.size).toBe(0) + await collection.cleanup() + }) + + it(`deterministically materializes a shared in-flight result after fast subset unmount and remount`, async () => { + const deferred = createDeferred>() + const queryFn = vi.fn(() => deferred.promise) + const collection = createCollection( + queryCollectionOptions({ + id: `fast-subset-remount-test`, + queryClient, + queryKey: [`fast-subset-remount-test`], + queryFn, + getKey, + syncMode: `on-demand`, + }), + ) + const firstLiveQuery = createSubset(collection) + void firstLiveQuery.preload().catch(() => undefined) + await vi.waitFor(() => expect(queryFn).toHaveBeenCalledTimes(1)) + + await firstLiveQuery.cleanup() + const secondLiveQuery = createSubset(collection) + const secondPreload = secondLiveQuery.preload() + deferred.resolve([{ id: `1`, name: `Remounted item` }]) + await secondPreload + + expect(queryFn).toHaveBeenCalledTimes(1) + expect(stripVirtualProps(collection.get(`1`))).toEqual({ + id: `1`, + name: `Remounted item`, + }) + await secondLiveQuery.cleanup() + expect(collection.size).toBe(0) + await collection.cleanup() + }) + + it(`keeps invalidate-unsubscribe-resubscribe compatible while removing stale subset rows and observers`, async () => { + let items: Array = [{ id: `1`, name: `Initial item` }] + const queryFn = vi.fn(() => Promise.resolve(items)) + const collection = createCollection( + queryCollectionOptions({ + id: `subset-invalidation-remount-test`, + queryClient, + queryKey: [`subset-invalidation-remount-test`], + queryFn, + getKey, + syncMode: `on-demand`, + }), + ) + const firstLiveQuery = createSubset(collection) + await firstLiveQuery.preload() + const subsetQuery = queryClient.getQueryCache().findAll({ + queryKey: [`subset-invalidation-remount-test`], + })[0] + expect(subsetQuery).toBeDefined() + + items = [{ id: `1`, name: `Invalidated item` }] + await queryClient.invalidateQueries({ + queryKey: subsetQuery!.queryKey, + exact: true, + }) + await vi.waitFor(() => + expect(collection.get(`1`)?.name).toBe(`Invalidated item`), + ) + + await firstLiveQuery.cleanup() + expect(collection.size).toBe(0) + expect(subsetQuery!.getObserversCount()).toBe(0) + + items = [{ id: `1`, name: `Remounted item` }] + const secondLiveQuery = createSubset(collection) + await secondLiveQuery.preload() + await queryClient.invalidateQueries({ + queryKey: subsetQuery!.queryKey, + exact: true, + }) + await vi.waitFor(() => + expect(collection.get(`1`)?.name).toBe(`Remounted item`), + ) + + await secondLiveQuery.cleanup() + expect(collection.size).toBe(0) + expect( + queryClient + .getQueryCache() + .findAll({ + queryKey: [`subset-invalidation-remount-test`], + })[0] + ?.getObserversCount() ?? 0, + ).toBe(0) + await collection.cleanup() + }) + }) + it(`should maintain data consistency during rapid updates`, async () => { const queryKey = [`rapid-updates-test`] let updateCount = 0 From d2a11b1b3cf250944eabbc10bcfdf3ebee4332ae Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 16 Jul 2026 14:03:51 -0600 Subject: [PATCH 49/58] fix(electric): bound refresh wait for on-demand subsets (#1575) * fix(electric): bound refresh wait for on-demand subsets * ci: apply automated fixes * test(electric): cover bounded refresh completion --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/tame-rn-live-poll-refresh.md | 5 + .../electric-db-collection/src/electric.ts | 22 +++- .../tests/electric.test.ts | 116 ++++++++++++++++++ 3 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 .changeset/tame-rn-live-poll-refresh.md diff --git a/.changeset/tame-rn-live-poll-refresh.md b/.changeset/tame-rn-live-poll-refresh.md new file mode 100644 index 0000000000..073897f828 --- /dev/null +++ b/.changeset/tame-rn-live-poll-refresh.md @@ -0,0 +1,5 @@ +--- +'@tanstack/electric-db-collection': patch +--- + +Bound the wait for Electric stream refreshes before loading on-demand subsets so native fetch implementations that do not promptly abort long polls do not keep live queries loading until the poll times out. diff --git a/packages/electric-db-collection/src/electric.ts b/packages/electric-db-collection/src/electric.ts index 22d222c155..1b63237cff 100644 --- a/packages/electric-db-collection/src/electric.ts +++ b/packages/electric-db-collection/src/electric.ts @@ -64,6 +64,8 @@ export { isChangeMessage, isControlMessage } from '@electric-sql/client' const debug = DebugModule.debug(`ts/db:electric`) +const FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250 + /** * Symbol for internal test hooks (hidden from public API) */ @@ -481,11 +483,23 @@ function createLoadSubsetDedupe>({ // When the stream is already up-to-date, it may be in a long-poll wait. // Forcing a disconnect-and-refresh ensures requestSnapshot gets a response // from a fresh server round-trip rather than waiting for the current poll to end. - // If the refresh fails (e.g., PauseLock held during subscriber processing in - // join pipelines), we fall through to requestSnapshot which still works. + // Some native fetch implementations (notably React Native/Expo) may not abort + // long-poll requests promptly. Bound the wait so on-demand live queries don't + // remain loading until the long-poll naturally times out. + // If the refresh fails or times out, we fall through to requestSnapshot which + // still works. if (stream.isUpToDate) { + let timeoutId: ReturnType | undefined try { - await stream.forceDisconnectAndRefresh() + await Promise.race([ + stream.forceDisconnectAndRefresh(), + new Promise((resolve) => { + timeoutId = setTimeout( + resolve, + FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS, + ) + }), + ]) } catch (error) { if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) { return @@ -494,6 +508,8 @@ function createLoadSubsetDedupe>({ `${logPrefix}forceDisconnectAndRefresh failed, proceeding to requestSnapshot: %o`, error, ) + } finally { + clearTimeout(timeoutId) } } diff --git a/packages/electric-db-collection/tests/electric.test.ts b/packages/electric-db-collection/tests/electric.test.ts index 8eabf0a15d..c48121a9c1 100644 --- a/packages/electric-db-collection/tests/electric.test.ts +++ b/packages/electric-db-collection/tests/electric.test.ts @@ -2582,6 +2582,122 @@ describe(`Electric Integration`, () => { expect(mockRequestSnapshot).toHaveBeenCalledTimes(1) }) + it(`should request the snapshot after the refresh timeout and ignore late fulfillment`, async () => { + vi.useFakeTimers() + try { + let resolveRefresh: () => void = () => {} + const refresh = new Promise((resolve) => { + resolveRefresh = resolve + }) + mockStream.isUpToDate = true + mockForceDisconnectAndRefresh.mockReturnValueOnce(refresh) + + const testCollection = createCollection( + electricCollectionOptions({ + id: `on-demand-refresh-timeout-fulfillment-test`, + shapeOptions: { + url: `http://test-url`, + params: { table: `test_table` }, + }, + syncMode: `on-demand`, + getKey: (item: Row) => item.id as number, + startSync: true, + }), + ) + + let loadSettled = false + const load = Promise.resolve( + testCollection._sync.loadSubset({ limit: 10 }), + ).then(() => { + loadSettled = true + }) + + await vi.advanceTimersByTimeAsync(249) + expect(mockRequestSnapshot).not.toHaveBeenCalled() + expect(loadSettled).toBe(false) + + await vi.advanceTimersByTimeAsync(1) + await load + expect(mockRequestSnapshot).toHaveBeenCalledTimes(1) + expect(loadSettled).toBe(true) + await testCollection.cleanup() + expect(vi.getTimerCount()).toBe(0) + + resolveRefresh() + await refresh + await Promise.resolve() + expect(mockRequestSnapshot).toHaveBeenCalledTimes(1) + expect(vi.getTimerCount()).toBe(0) + } finally { + vi.useRealTimers() + } + }) + + it(`should handle late refresh rejection after requesting the snapshot`, async () => { + vi.useFakeTimers() + try { + let rejectRefresh: (error: Error) => void = () => {} + const refresh = new Promise((_resolve, reject) => { + rejectRefresh = reject + }) + mockStream.isUpToDate = true + mockForceDisconnectAndRefresh.mockReturnValueOnce(refresh) + + const testCollection = createCollection( + electricCollectionOptions({ + id: `on-demand-refresh-timeout-rejection-test`, + shapeOptions: { + url: `http://test-url`, + params: { table: `test_table` }, + }, + syncMode: `on-demand`, + getKey: (item: Row) => item.id as number, + startSync: true, + }), + ) + + const load = testCollection._sync.loadSubset({ limit: 10 }) + await vi.advanceTimersByTimeAsync(250) + await load + + rejectRefresh(new Error(`late refresh failure`)) + await expect(refresh).rejects.toThrow(`late refresh failure`) + await Promise.resolve() + expect(mockRequestSnapshot).toHaveBeenCalledTimes(1) + expect(vi.getTimerCount()).toBe(0) + } finally { + vi.useRealTimers() + } + }) + + it(`should clear the refresh timeout when refresh settles early`, async () => { + vi.useFakeTimers() + try { + mockStream.isUpToDate = true + mockForceDisconnectAndRefresh.mockResolvedValueOnce(undefined) + + const testCollection = createCollection( + electricCollectionOptions({ + id: `on-demand-refresh-clears-timeout-test`, + shapeOptions: { + url: `http://test-url`, + params: { table: `test_table` }, + }, + syncMode: `on-demand`, + getKey: (item: Row) => item.id as number, + startSync: true, + }), + ) + + await testCollection._sync.loadSubset({ limit: 10 }) + + expect(mockRequestSnapshot).toHaveBeenCalledTimes(1) + expect(vi.getTimerCount()).toBe(0) + } finally { + vi.useRealTimers() + } + }) + it(`should fetch snapshots in progressive mode when loadSubset is called before sync completes`, async () => { vi.clearAllMocks() From 82b90a9e3a67cd8516f9c1a998b3ef325c2f508b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:13:46 -0600 Subject: [PATCH 50/58] docs: regenerate API documentation (#1629) Co-authored-by: github-actions[bot] --- docs/reference/interfaces/Transaction.md | 8 ++++---- .../functions/queryCollectionOptions.md | 8 ++++---- .../type-aliases/ApplyJoinOptionalityToMergedSchema.md | 2 +- docs/reference/type-aliases/FunctionalHavingRow.md | 2 +- docs/reference/type-aliases/GetResult.md | 2 +- docs/reference/type-aliases/GroupByCallback.md | 2 +- docs/reference/type-aliases/InferResultType.md | 2 +- docs/reference/type-aliases/JoinOnCallback.md | 2 +- .../reference/type-aliases/MergeContextForJoinCallback.md | 2 +- docs/reference/type-aliases/MergeContextWithJoinType.md | 2 +- docs/reference/type-aliases/OrderByCallback.md | 2 +- docs/reference/type-aliases/Prettify.md | 2 +- docs/reference/type-aliases/Ref.md | 4 ++-- docs/reference/type-aliases/RefsForContext.md | 2 +- docs/reference/type-aliases/WithResult.md | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/reference/interfaces/Transaction.md b/docs/reference/interfaces/Transaction.md index b5f6c3b4da..38faa9ccac 100644 --- a/docs/reference/interfaces/Transaction.md +++ b/docs/reference/interfaces/Transaction.md @@ -169,7 +169,7 @@ Array of new mutations to apply commit(): Promise>; ``` -Defined in: [packages/db/src/transactions.ts:481](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L481) +Defined in: [packages/db/src/transactions.ts:493](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L493) Commit the transaction and execute the mutation function @@ -228,7 +228,7 @@ console.log(tx.state) // "completed" or "failed" compareCreatedAt(other): number; ``` -Defined in: [packages/db/src/transactions.ts:535](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L535) +Defined in: [packages/db/src/transactions.ts:547](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L547) Compare two transactions by their createdAt time and sequence number in order to sort them in the order they were created. @@ -331,7 +331,7 @@ await tx.commit() rollback(config?): Transaction; ``` -Defined in: [packages/db/src/transactions.ts:398](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L398) +Defined in: [packages/db/src/transactions.ts:410](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L410) Rollback the transaction and any conflicting transactions @@ -418,7 +418,7 @@ Defined in: [packages/db/src/transactions.ts:239](https://github.com/TanStack/db touchCollection(): void; ``` -Defined in: [packages/db/src/transactions.ts:426](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L426) +Defined in: [packages/db/src/transactions.ts:438](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L438) #### Returns diff --git a/docs/reference/query-db-collection/functions/queryCollectionOptions.md b/docs/reference/query-db-collection/functions/queryCollectionOptions.md index 06c48a6d49..1e3a3e539c 100644 --- a/docs/reference/query-db-collection/functions/queryCollectionOptions.md +++ b/docs/reference/query-db-collection/functions/queryCollectionOptions.md @@ -11,7 +11,7 @@ title: queryCollectionOptions function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:438](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L438) +Defined in: [packages/query-db-collection/src/query.ts:445](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L445) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -151,7 +151,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:473](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L473) +Defined in: [packages/query-db-collection/src/query.ts:480](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L480) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -291,7 +291,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:506](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L506) +Defined in: [packages/query-db-collection/src/query.ts:513](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L513) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -423,7 +423,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:540](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L540) +Defined in: [packages/query-db-collection/src/query.ts:547](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L547) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. diff --git a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md index 7173e38303..68828fad88 100644 --- a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md +++ b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md @@ -9,7 +9,7 @@ title: ApplyJoinOptionalityToMergedSchema type ApplyJoinOptionalityToMergedSchema = { [K in keyof TExistingSchema]: K extends TFromSourceNames ? TJoinType extends "right" | "full" ? TExistingSchema[K] | undefined : TExistingSchema[K] : TExistingSchema[K] } & { [K in keyof TNewSchema]: TJoinType extends "left" | "full" ? TNewSchema[K] | undefined : TNewSchema[K] }; ``` -Defined in: [packages/db/src/query/builder/types.ts:929](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L929) +Defined in: [packages/db/src/query/builder/types.ts:984](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L984) ApplyJoinOptionalityToMergedSchema - Applies optionality rules when merging schemas diff --git a/docs/reference/type-aliases/FunctionalHavingRow.md b/docs/reference/type-aliases/FunctionalHavingRow.md index e5aaf3ec95..3b720db9aa 100644 --- a/docs/reference/type-aliases/FunctionalHavingRow.md +++ b/docs/reference/type-aliases/FunctionalHavingRow.md @@ -9,7 +9,7 @@ title: FunctionalHavingRow type FunctionalHavingRow = TContext["schema"] & TContext["hasResult"] extends true ? object : object; ``` -Defined in: [packages/db/src/query/builder/types.ts:589](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L589) +Defined in: [packages/db/src/query/builder/types.ts:640](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L640) FunctionalHavingRow - Type for the row parameter in functional having callbacks diff --git a/docs/reference/type-aliases/GetResult.md b/docs/reference/type-aliases/GetResult.md index 6e2a58c0b0..01f098bcb1 100644 --- a/docs/reference/type-aliases/GetResult.md +++ b/docs/reference/type-aliases/GetResult.md @@ -9,7 +9,7 @@ title: GetResult type GetResult = Prettify>; ``` -Defined in: [packages/db/src/query/builder/types.ts:1043](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1043) +Defined in: [packages/db/src/query/builder/types.ts:1098](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1098) ## Type Parameters diff --git a/docs/reference/type-aliases/GroupByCallback.md b/docs/reference/type-aliases/GroupByCallback.md index 62b0a172b9..077618195c 100644 --- a/docs/reference/type-aliases/GroupByCallback.md +++ b/docs/reference/type-aliases/GroupByCallback.md @@ -9,7 +9,7 @@ title: GroupByCallback type GroupByCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:552](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L552) +Defined in: [packages/db/src/query/builder/types.ts:603](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L603) GroupByCallback - Type for groupBy clause callback functions diff --git a/docs/reference/type-aliases/InferResultType.md b/docs/reference/type-aliases/InferResultType.md index b822c47b5d..bd6de7a3d9 100644 --- a/docs/reference/type-aliases/InferResultType.md +++ b/docs/reference/type-aliases/InferResultType.md @@ -9,7 +9,7 @@ title: InferResultType type InferResultType = TContext extends SingleResult ? GetResult | undefined : GetResult[]; ``` -Defined in: [packages/db/src/query/builder/types.ts:955](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L955) +Defined in: [packages/db/src/query/builder/types.ts:1010](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1010) Utility type to infer the query result size (single row or an array) diff --git a/docs/reference/type-aliases/JoinOnCallback.md b/docs/reference/type-aliases/JoinOnCallback.md index 31566ca223..922b693ec0 100644 --- a/docs/reference/type-aliases/JoinOnCallback.md +++ b/docs/reference/type-aliases/JoinOnCallback.md @@ -9,7 +9,7 @@ title: JoinOnCallback type JoinOnCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:568](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L568) +Defined in: [packages/db/src/query/builder/types.ts:619](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L619) JoinOnCallback - Type for join condition callback functions diff --git a/docs/reference/type-aliases/MergeContextForJoinCallback.md b/docs/reference/type-aliases/MergeContextForJoinCallback.md index 0c0cfa8d35..6ac3820075 100644 --- a/docs/reference/type-aliases/MergeContextForJoinCallback.md +++ b/docs/reference/type-aliases/MergeContextForJoinCallback.md @@ -9,7 +9,7 @@ title: MergeContextForJoinCallback type MergeContextForJoinCallback = object & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; ``` -Defined in: [packages/db/src/query/builder/types.ts:1200](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1200) +Defined in: [packages/db/src/query/builder/types.ts:1255](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1255) MergeContextForJoinCallback - Special context for join condition callbacks diff --git a/docs/reference/type-aliases/MergeContextWithJoinType.md b/docs/reference/type-aliases/MergeContextWithJoinType.md index 0ea2e91d31..e5655ebf6d 100644 --- a/docs/reference/type-aliases/MergeContextWithJoinType.md +++ b/docs/reference/type-aliases/MergeContextWithJoinType.md @@ -9,7 +9,7 @@ title: MergeContextWithJoinType type MergeContextWithJoinType = object & PreserveSingleResultFlag & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; ``` -Defined in: [packages/db/src/query/builder/types.ts:871](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L871) +Defined in: [packages/db/src/query/builder/types.ts:926](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L926) MergeContextWithJoinType - Creates a new context after a join operation diff --git a/docs/reference/type-aliases/OrderByCallback.md b/docs/reference/type-aliases/OrderByCallback.md index 05f7bc5d94..783862a6f4 100644 --- a/docs/reference/type-aliases/OrderByCallback.md +++ b/docs/reference/type-aliases/OrderByCallback.md @@ -9,7 +9,7 @@ title: OrderByCallback type OrderByCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:516](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L516) +Defined in: [packages/db/src/query/builder/types.ts:567](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L567) OrderByCallback - Type for orderBy clause callback functions diff --git a/docs/reference/type-aliases/Prettify.md b/docs/reference/type-aliases/Prettify.md index 2c77515148..89092056f7 100644 --- a/docs/reference/type-aliases/Prettify.md +++ b/docs/reference/type-aliases/Prettify.md @@ -9,7 +9,7 @@ title: Prettify type Prettify = { [K in keyof T]: T[K] } & object; ``` -Defined in: [packages/db/src/query/builder/types.ts:1242](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1242) +Defined in: [packages/db/src/query/builder/types.ts:1297](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1297) Prettify - Utility type for clean IDE display diff --git a/docs/reference/type-aliases/Ref.md b/docs/reference/type-aliases/Ref.md index be97f893e9..5543f58749 100644 --- a/docs/reference/type-aliases/Ref.md +++ b/docs/reference/type-aliases/Ref.md @@ -6,10 +6,10 @@ title: Ref # Type Alias: Ref\ ```ts -type Ref = { [K in keyof T]: IsNonExactOptional extends true ? IsNonExactNullable extends true ? IsPlainObject> extends true ? Ref, Nullable> | undefined : RefLeaf, Nullable> | undefined : IsPlainObject> extends true ? Ref, Nullable> | undefined : RefLeaf, Nullable> | undefined : IsNonExactNullable extends true ? IsPlainObject> extends true ? Ref, Nullable> | null : RefLeaf, Nullable> | null : IsPlainObject extends true ? Ref : RefLeaf } & RefLeaf & VirtualPropsRef; +type Ref = T extends unknown ? RefBranch : never; ``` -Defined in: [packages/db/src/query/builder/types.ts:773](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L773) +Defined in: [packages/db/src/query/builder/types.ts:824](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L824) Ref - The user-facing ref interface for the query builder diff --git a/docs/reference/type-aliases/RefsForContext.md b/docs/reference/type-aliases/RefsForContext.md index 6eeb1b161f..9e011cb420 100644 --- a/docs/reference/type-aliases/RefsForContext.md +++ b/docs/reference/type-aliases/RefsForContext.md @@ -9,7 +9,7 @@ title: RefsForContext type RefsForContext = { [K in KeysOfUnion>]: IsNonExactOptional, K>> extends true ? IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>>, true> : IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>> } & TContext["hasResult"] extends true ? object : object; ``` -Defined in: [packages/db/src/query/builder/types.ts:623](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L623) +Defined in: [packages/db/src/query/builder/types.ts:674](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L674) ## Type Parameters diff --git a/docs/reference/type-aliases/WithResult.md b/docs/reference/type-aliases/WithResult.md index e906c97ad0..65160460f7 100644 --- a/docs/reference/type-aliases/WithResult.md +++ b/docs/reference/type-aliases/WithResult.md @@ -9,7 +9,7 @@ title: WithResult type WithResult = Prettify & object>; ``` -Defined in: [packages/db/src/query/builder/types.ts:1232](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1232) +Defined in: [packages/db/src/query/builder/types.ts:1287](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1287) WithResult - Updates a context with a new result type after select() From 4d0b1c0aef5a0bfa7d26e45c39e6878f30a986a6 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 16 Jul 2026 14:15:25 -0600 Subject: [PATCH 51/58] docs(query-db-collection): document scoped collection factories (#1671) * docs(query-db-collection): document scoped collection factories * docs: simplify scoped collection guidance * docs: add scoped factory changeset * docs: remove unnecessary changeset --- docs/collections/query-collection.md | 71 ++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 1c75f022dd..c2635453be 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -112,25 +112,76 @@ export function getTodosCollection( Avoid calling `createCollection(todoCollectionOptions(queryClient))` independently during render or in each consumer. Share the stable collection instance for the lifetime of that `QueryClient` scope. -The same pattern works for scoped or parameterized collections. Pass route params, a tenant ID, or filters into the factory alongside the `QueryClient`: +This keeps SSR and request-scoped code from sharing a global `QueryClient` while keeping each collection instance stable within its scope. + +### Business-Scoped Collection Factories + +A tenant, project, account, or route parameter can define a **business scope**: the server resource that a collection represents. Include the scope in both the Query key and `queryFn`. This extends the [runtime `QueryClient` factory pattern](#creating-collection-options-from-a-runtime-queryclient) with an explicit scope parameter: ```typescript -export function projectTodosCollectionOptions( +interface Todo { + id: string + title: string + projectId: string +} + +async function fetchProjectTodos(projectId: string): Promise> { + const response = await fetch(`/api/projects/${projectId}/todos`) + return response.json() +} + +export function createProjectTodosCollection( queryClient: QueryClient, projectId: string, ) { - return queryCollectionOptions({ - queryKey: ["projects", projectId, "todos"], - queryFn: () => fetchProjectTodos(projectId), - queryClient, - getKey: (todo) => todo.id, - }) + return createCollection( + queryCollectionOptions({ + queryKey: ["projects", projectId, "todos"], + queryFn: () => fetchProjectTodos(projectId), + queryClient, + getKey: (todo) => todo.id, + }) + ) } ``` -For parameterized collections, memoize by both the scoped `QueryClient` and the parameter tuple. If a long-lived scope can create unbounded parameter sets, add eviction or dispose collections when they are no longer needed. +The scope is part of the collection's identity. Memoize by both the `QueryClient` and a stable scope key so consumers of the same project share one collection: -This keeps SSR and request-scoped code from sharing a global `QueryClient` while keeping each collection instance stable within its scope. +```typescript +type ProjectTodosCollection = ReturnType + +const projectCollections = new WeakMap< + QueryClient, + Map +>() + +export function getProjectTodosCollection( + queryClient: QueryClient, + projectId: string, +): ProjectTodosCollection { + let collectionsByProject = projectCollections.get(queryClient) + + if (!collectionsByProject) { + collectionsByProject = new Map() + projectCollections.set(queryClient, collectionsByProject) + } + + let collection = collectionsByProject.get(projectId) + + if (!collection) { + collection = createProjectTodosCollection(queryClient, projectId) + collectionsByProject.set(projectId, collection) + } + + return collection +} +``` + +For multiple scope values, use nested maps or a collision-safe stable key that includes every value. Do not call the factory on each render. In a long-lived client, user-selected scopes can make the map grow without bound. Remove unused entries and call `await collection.cleanup()` when your application owns their lifecycle. Request-local maps can be discarded with the request. + +A business scope is separate from a **relational subset** requested by a live query. With `syncMode: "on-demand"`, `LoadSubsetOptions` describes predicates, ordering, limits, and offsets within one business-scoped collection. These options reach `queryFn` through `ctx.meta.loadSubsetOptions` and determine the subset Query keys. See [QueryFn and Predicate Push-Down](#queryfn-and-predicate-push-down). + +Do not create a collection for each `where`, `orderBy`, or `limit`. Reuse the business-scoped collection and let on-demand loading represent those subsets. Create separate collections only for distinct server resources. ### Query Options From b850340877a7644092b54d3cd92b903c903e106e Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 16 Jul 2026 14:30:37 -0600 Subject: [PATCH 52/58] docs: add contributing guide (#1620) * docs: add contributing guide * docs: address contributing review feedback * docs: order development setup steps --- CONTRIBUTING.md | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..cc6f65a9e7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,146 @@ +# Contributing + +## Questions + +If you have questions about implementation details, help, or support, please use our dedicated community forum at [GitHub Discussions](https://github.com/TanStack/db/discussions). **PLEASE NOTE:** If you choose to open an issue for your question instead, your issue may be closed and redirected to the forum. + +## Reporting issues + +If you have found what you think is a bug, please [file an issue](https://github.com/TanStack/db/issues/new/choose). **PLEASE NOTE:** Issues that are identified as implementation questions or non-issues may be closed and redirected to [GitHub Discussions](https://github.com/TanStack/db/discussions). + +## Suggesting new features + +If you are here to suggest a feature, first create an issue if it does not already exist. From there, we can discuss use cases for the feature and how it could be implemented. + +## Development + +If you have been assigned to fix an issue or develop a new feature, please follow these steps to get started: + +- Fork this repository. +- Use the Node.js version mentioned in `.nvmrc`. + + ```bash + nvm use + ``` + +- Enable [Corepack](https://nodejs.org/api/corepack.html) so the [pnpm](https://pnpm.io/) version mentioned in `package.json` is used. + + ```bash + corepack enable + ``` + +- Install dependencies. + + ```bash + pnpm install + ``` + +- Build all packages. + + ```bash + pnpm build + ``` + +- Run tests. + + ```bash + pnpm test + ``` + +- Run linting. + + ```bash + pnpm lint + ``` + +- Implement your changes and tests in the relevant package or example. +- Document your changes in the appropriate doc page. +- Git stage your required changes and commit them. +- Submit a PR for review. + +### Editing the docs locally and previewing changes + +The documentation for all TanStack projects is hosted on [tanstack.com](https://tanstack.com), which is a TanStack Start application (https://github.com/TanStack/tanstack.com). You need to run this app locally to preview your changes in the `TanStack/db` docs. + +> [!NOTE] +> The website fetches doc pages from GitHub in production, and searches for them at `../db/docs` in development. Your local clone of `TanStack/db` needs to be in the same directory as the local clone of `TanStack/tanstack.com`. + +You can follow these steps to set up the docs for local development: + +1. Make a new directory called `tanstack`. + +```sh +mkdir tanstack +``` + +2. Enter that directory and clone the [`TanStack/db`](https://github.com/TanStack/db) and [`TanStack/tanstack.com`](https://github.com/TanStack/tanstack.com) repos. + +```sh +cd tanstack +git clone git@github.com:TanStack/db.git +# We probably don't need all the branches and commit history +# from the `tanstack.com` repo, so let's just create a shallow +# clone of the latest version of the `main` branch. +# Read more about shallow clones here: +# https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/#user-content-shallow-clones +git clone git@github.com:TanStack/tanstack.com.git --depth=1 --single-branch --branch=main +``` + +> [!NOTE] +> Your `tanstack` directory should look like this: +> +> ```text +> tanstack/ +> | +> +-- db/ (<-- this directory cannot be called anything else!) +> | +> +-- tanstack.com/ +> ``` + +3. Enter the `tanstack/tanstack.com` directory, install the dependencies, and run the app in dev mode. + +```sh +cd tanstack.com +pnpm i +# The app will run on http://localhost:3000 by default +pnpm dev +``` + +4. Visit http://localhost:3000/db/latest/docs/overview in the browser and see the changes you make in `tanstack/db/docs` there. + +> [!WARNING] +> You will need to update `docs/config.json` if you add a new documentation page. + +### Running examples + +- Make sure you've installed dependencies in the repo's root directory. + + ```bash + pnpm install + ``` + +- If you want to run an example against your local changes, run the relevant package build/watch command from the repo root if needed. Otherwise, examples may run against the latest published TanStack DB release. + +- Run the example from the selected example directory. + + ```bash + pnpm dev + ``` + +#### Note on standalone execution + +If you want to run an example without installing dependencies for the whole repo, follow the instructions from the example's README.md file. It will then run against the latest TanStack DB release. + +## Changesets + +This repo uses [Changesets](https://github.com/changesets/changesets) to automate releases. If your PR should release a new package version (patch, minor, or major), please run `pnpm changeset` and commit the generated file. If your PR affects docs, examples, styles, etc., you probably don't need to generate a changeset. + +## Pull requests + +Maintainers merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface. + +Use an appropriate commit type. Be especially careful with breaking changes. + +## Releases + +For each new commit added to `main`, a GitHub Workflow is triggered which runs the [Changesets Action](https://github.com/changesets/action). This generates a preview PR showing the impact of all changesets. When this PR is merged, the package will be published to npm. From 8258d0955ab47c8510bd49ea59bcdbefd2ae054d Mon Sep 17 00:00:00 2001 From: Oliver Beattie Date: Thu, 16 Jul 2026 23:18:23 +0200 Subject: [PATCH 53/58] fix(db): restore query typechecking for generic collection row types (#1678) * test(db): add failing type tests for generic collection row types (#1677) Queries over a Collection where T is an unresolved generic type parameter stopped typechecking in 0.6.6: refs inside where/join/select callbacks resolve to a union including RefLeaf, which exposes no row properties, even ones guaranteed by T's constraint. These tests currently fail and should pass once the ref typing is fixed. Co-Authored-By: Claude Fable 5 * fix(db): restore query typechecking for generic collection row types Fixes #1677. Since 0.6.6, refs inside where/join/select callbacks broke when the collection's row type is an unresolved generic type parameter: TypeScript defers the IsPlainObject conditional in RefForContextValue and degrades the ref to a union that includes RefLeaf, which exposes no row properties. Two changes: - Make RefForContextValue distribute over T so that, when deferred, TypeScript resolves its constraint by instantiating T with T's own constraint, picking the Ref branch instead of the Ref | RefLeaf union. - Use GetRawResult instead of GetResult in SchemaFromSource for subquery sources: GetResult's Prettify wrapper produces a mapped type that the conditional-constraint resolution above cannot see through. Final query results still go through GetResult, so IDE display is unaffected. Co-Authored-By: Claude Fable 5 * chore: add changeset for generic row type fix Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- .changeset/generic-row-type-refs.md | 5 ++ packages/db/src/query/builder/types.ts | 9 ++-- .../tests/query/generic-collection.test-d.ts | 53 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 .changeset/generic-row-type-refs.md create mode 100644 packages/db/tests/query/generic-collection.test-d.ts diff --git a/.changeset/generic-row-type-refs.md b/.changeset/generic-row-type-refs.md new file mode 100644 index 0000000000..5bac806697 --- /dev/null +++ b/.changeset/generic-row-type-refs.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Fix queries failing to typecheck when the collection's row type is a generic type parameter. Refs inside where/join/select callbacks now expose the properties guaranteed by the type parameter's constraint, and subqueries over generic collections can be used as join sources again (regression introduced in 0.6.6). diff --git a/packages/db/src/query/builder/types.ts b/packages/db/src/query/builder/types.ts index d8b730e76b..f8cdbcabe4 100644 --- a/packages/db/src/query/builder/types.ts +++ b/packages/db/src/query/builder/types.ts @@ -117,7 +117,7 @@ export type SchemaFromSource = Prettify<{ [K in keyof T]: T[K] extends CollectionImpl ? InferCollectionType : T[K] extends QueryBuilder - ? GetResult + ? GetRawResult : never }> @@ -662,8 +662,11 @@ type ValueOfUnion = T extends unknown ? T[K] : never : never -type RefForContextValue = - IsPlainObject extends true ? Ref : RefLeaf +type RefForContextValue = T extends unknown + ? IsPlainObject extends true + ? Ref + : RefLeaf + : never type RefsSchemaForContext = IsExactlyUndefined extends true ? TContext[`schema`] diff --git a/packages/db/tests/query/generic-collection.test-d.ts b/packages/db/tests/query/generic-collection.test-d.ts new file mode 100644 index 0000000000..553b5f975b --- /dev/null +++ b/packages/db/tests/query/generic-collection.test-d.ts @@ -0,0 +1,53 @@ +import { describe, test } from 'vitest' +import { createLiveQueryCollection, eq } from '../../src/query/index.js' +import type { Collection } from '../../src/collection/index.js' + +// Regression tests for https://github.com/TanStack/db/issues/1677 +// Queries over a Collection where T is an unresolved generic type parameter +// must still expose the properties guaranteed by T's constraint inside +// where/join/select callbacks. This worked in 0.6.5 and broke in 0.6.6. + +describe(`queries over generic collection row types`, () => { + test(`where callback can access properties guaranteed by the type constraint`, () => { + function findById( + items: Collection, + id: string, + ) { + return createLiveQueryCollection((q) => + q.from({ items }).where(({ items: itemsRef }) => eq(itemsRef.id, id)), + ) + } + + void findById + }) + + test(`select callback can access properties guaranteed by the type constraint`, () => { + function selectIds(items: Collection) { + return createLiveQueryCollection((q) => + q.from({ items }).select(({ items: itemsRef }) => ({ + id: itemsRef.id, + })), + ) + } + + void selectIds + }) + + test(`subquery over a generic collection can be used as a join source`, () => { + function withDiff( + a: Collection, + b: Collection<{ id: string }, string>, + ) { + return createLiveQueryCollection((q) => { + const sub = q.from({ a }) + return q + .from({ b }) + .leftJoin({ sub }, ({ b: bRef, sub: subRef }) => + eq(bRef.id, subRef.id), + ) + }) + } + + void withDiff + }) +}) From f89fad1b0f3d83dd51b632ef08f83e3285d25684 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:34:03 -0600 Subject: [PATCH 54/58] ci: Version Packages (#1670) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/characterize-query-cleanup.md | 5 -- .changeset/extract-query-ownership-helpers.md | 5 -- .changeset/generic-row-type-refs.md | 5 -- .changeset/live-query-gctime-zero.md | 5 -- .changeset/progressive-persisted-resume.md | 5 -- .changeset/query-options-pass-through.md | 5 -- .changeset/tame-rn-live-poll-refresh.md | 5 -- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 7 ++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 8 +++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 11 +++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 15 ++++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 65 files changed, 276 insertions(+), 127 deletions(-) delete mode 100644 .changeset/characterize-query-cleanup.md delete mode 100644 .changeset/extract-query-ownership-helpers.md delete mode 100644 .changeset/generic-row-type-refs.md delete mode 100644 .changeset/live-query-gctime-zero.md delete mode 100644 .changeset/progressive-persisted-resume.md delete mode 100644 .changeset/query-options-pass-through.md delete mode 100644 .changeset/tame-rn-live-poll-refresh.md diff --git a/.changeset/characterize-query-cleanup.md b/.changeset/characterize-query-cleanup.md deleted file mode 100644 index 9f41155f97..0000000000 --- a/.changeset/characterize-query-cleanup.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/query-db-collection': patch ---- - -Fix temporary query readiness listeners so subset unload and collection cleanup release them correctly during in-flight requests. diff --git a/.changeset/extract-query-ownership-helpers.md b/.changeset/extract-query-ownership-helpers.md deleted file mode 100644 index df07298f03..0000000000 --- a/.changeset/extract-query-ownership-helpers.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/query-db-collection': patch ---- - -Extract internal query row ownership helpers to make lifecycle cleanup paths easier to reason about while preserving existing behavior. diff --git a/.changeset/generic-row-type-refs.md b/.changeset/generic-row-type-refs.md deleted file mode 100644 index 5bac806697..0000000000 --- a/.changeset/generic-row-type-refs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Fix queries failing to typecheck when the collection's row type is a generic type parameter. Refs inside where/join/select callbacks now expose the properties guaranteed by the type parameter's constraint, and subqueries over generic collections can be used as join sources again (regression introduced in 0.6.6). diff --git a/.changeset/live-query-gctime-zero.md b/.changeset/live-query-gctime-zero.md deleted file mode 100644 index ac199c4e55..0000000000 --- a/.changeset/live-query-gctime-zero.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Preserve an explicit `gcTime: 0` on live query collections. The live query config builder used `this.config.gcTime || 5000`, so a `gcTime` of `0` (which disables garbage collection) was treated as unset and silently replaced by the 5s default, causing the collection to be garbage collected instead of kept alive. Use `??` so only `undefined` falls back to the default. diff --git a/.changeset/progressive-persisted-resume.md b/.changeset/progressive-persisted-resume.md deleted file mode 100644 index d23766fa81..0000000000 --- a/.changeset/progressive-persisted-resume.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/electric-db-collection': patch ---- - -Prevent progressive Electric collections from truncating persisted rows when resuming from saved Electric shape metadata. diff --git a/.changeset/query-options-pass-through.md b/.changeset/query-options-pass-through.md deleted file mode 100644 index 70a48e43ec..0000000000 --- a/.changeset/query-options-pass-through.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/query-db-collection': minor ---- - -Add top-level Query Collection support for additional Query observer options while preserving QueryClient defaultOptions behavior. diff --git a/.changeset/tame-rn-live-poll-refresh.md b/.changeset/tame-rn-live-poll-refresh.md deleted file mode 100644 index 073897f828..0000000000 --- a/.changeset/tame-rn-live-poll-refresh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/electric-db-collection': patch ---- - -Bound the wait for Electric stream refreshes before loading on-demand subsets so native fetch implementations that do not promptly abort long polls do not keep live queries loading until the poll times out. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index 8dffb3be46..26d55fd858 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.75", - "@tanstack/db": "^0.6.15", + "@tanstack/angular-db": "^0.1.76", + "@tanstack/db": "^0.6.16", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index 63de4d9ab9..42131dd1c9 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.19", - "@tanstack/node-db-sqlite-persistence": "^0.2.7", - "@tanstack/offline-transactions": "^1.0.40", - "@tanstack/query-db-collection": "^1.0.48", - "@tanstack/react-db": "^0.1.93", + "@tanstack/electron-db-sqlite-persistence": "^0.1.20", + "@tanstack/node-db-sqlite-persistence": "^0.2.8", + "@tanstack/offline-transactions": "^1.0.41", + "@tanstack/query-db-collection": "^1.1.0", + "@tanstack/react-db": "^0.1.94", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 246d598225..6289136338 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.15", - "@tanstack/offline-transactions": "^1.0.40", - "@tanstack/query-db-collection": "^1.0.48", - "@tanstack/react-db": "^0.1.93", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.7", + "@tanstack/db": "^0.6.16", + "@tanstack/offline-transactions": "^1.0.41", + "@tanstack/query-db-collection": "^1.1.0", + "@tanstack/react-db": "^0.1.94", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.8", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 455015aaba..a63a641093 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.15", - "@tanstack/electric-db-collection": "^0.3.13", - "@tanstack/offline-transactions": "^1.0.40", - "@tanstack/react-db": "^0.1.93", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.7", + "@tanstack/db": "^0.6.16", + "@tanstack/electric-db-collection": "^0.3.14", + "@tanstack/offline-transactions": "^1.0.41", + "@tanstack/react-db": "^0.1.94", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.8", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index 68f0819bd4..31ffdc09f4 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.7", - "@tanstack/db": "^0.6.15", - "@tanstack/offline-transactions": "^1.0.40", - "@tanstack/query-db-collection": "^1.0.48", - "@tanstack/react-db": "^0.1.93", + "@tanstack/browser-db-sqlite-persistence": "^0.2.8", + "@tanstack/db": "^0.6.16", + "@tanstack/offline-transactions": "^1.0.41", + "@tanstack/query-db-collection": "^1.1.0", + "@tanstack/react-db": "^0.1.94", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 33e86546a4..6441cd1d3b 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.15", - "@tanstack/react-db": "^0.1.93", + "@tanstack/db": "^0.6.16", + "@tanstack/react-db": "^0.1.94", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 168de27bdc..2d69ccbb5a 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.48", - "@tanstack/react-db": "^0.1.93", + "@tanstack/query-db-collection": "^1.1.0", + "@tanstack/react-db": "^0.1.94", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index b288d50bf7..2935606209 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.13", + "@tanstack/electric-db-collection": "^0.3.14", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.48", - "@tanstack/react-db": "^0.1.93", + "@tanstack/query-db-collection": "^1.1.0", + "@tanstack/react-db": "^0.1.94", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.93", + "@tanstack/trailbase-db-collection": "^0.1.94", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index 09c6177e74..95e0616db0 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.13", + "@tanstack/electric-db-collection": "^0.3.14", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.48", - "@tanstack/solid-db": "^0.2.29", + "@tanstack/query-db-collection": "^1.1.0", + "@tanstack/solid-db": "^0.2.30", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.93", + "@tanstack/trailbase-db-collection": "^0.1.94", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index dec7533dc7..e92e0c88f1 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.76 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.1.75 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 36a4202673..eda106fc25 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.75", + "version": "0.1.76", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index 7d2b10e469..dcebd33ec9 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.8 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.2.7 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 4c8a2e0f7c..de64e8fccb 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.7", + "version": "0.2.8", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 1e0f638c88..aaf56287f3 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.8 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.2.7 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index 7656bbae25..5feabbd34f 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.20 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + - @tanstack/capacitor-db-sqlite-persistence@0.2.8 + ## 0.0.19 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 30acd2d6c4..f769604a1e 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.19", + "version": "0.0.20", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index c3d2d976ad..48707c77e4 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.7", + "version": "0.2.8", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index 776cf0c26b..1c0203475c 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.8 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.2.7 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 1c96a630b1..728f6fce01 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.7", + "version": "0.2.8", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index 09d0edaea3..3e89e46a1f 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.8 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.2.7 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 9756a942aa..be2341c0e2 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.7", + "version": "0.2.8", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index 556df7285e..57ca69702c 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/db +## 0.6.16 + +### Patch Changes + +- Fix queries failing to typecheck when the collection's row type is a generic type parameter. Refs inside where/join/select callbacks now expose the properties guaranteed by the type parameter's constraint, and subqueries over generic collections can be used as join sources again (regression introduced in 0.6.6). ([#1678](https://github.com/TanStack/db/pull/1678)) + +- Preserve an explicit `gcTime: 0` on live query collections. The live query config builder used `this.config.gcTime || 5000`, so a `gcTime` of `0` (which disables garbage collection) was treated as unset and silently replaced by the 5s default, causing the collection to be garbage collected instead of kept alive. Use `??` so only `undefined` falls back to the default. ([#1660](https://github.com/TanStack/db/pull/1660)) + ## 0.6.15 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 6e94d9a709..f99b6fe435 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.15", + "version": "0.6.16", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index c9677ebb0b..b698868549 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,16 @@ # @tanstack/electric-db-collection +## 0.3.14 + +### Patch Changes + +- Prevent progressive Electric collections from truncating persisted rows when resuming from saved Electric shape metadata. ([#1493](https://github.com/TanStack/db/pull/1493)) + +- Bound the wait for Electric stream refreshes before loading on-demand subsets so native fetch implementations that do not promptly abort long polls do not keep live queries loading until the poll times out. ([#1575](https://github.com/TanStack/db/pull/1575)) + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.3.13 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index c42d885abc..23da8ac76f 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.13", + "version": "0.3.14", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 9aa150d58f..9662079f5a 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.20 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.1.19 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index bbc138691a..345d506fdd 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.19", + "version": "0.1.20", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index f5c7934423..fb65632a3f 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.8 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.2.7 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index eb4ef71ec5..92509453f5 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.20 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + - @tanstack/expo-db-sqlite-persistence@0.2.8 + ## 0.0.19 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 28f2228309..351f644dfc 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.19", + "version": "0.0.20", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 4c8590527a..2cc04d03b5 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.7", + "version": "0.2.8", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index 198b3d891a..f9d57554ca 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.8 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.2.7 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index ab9cf69355..48b856e1ed 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.7", + "version": "0.2.8", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index 8ccd643a78..dc66c1b59b 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.41 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 1.0.40 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index da694381ad..bc8294f67f 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.40", + "version": "1.0.41", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index 24ca9d7d57..365bf47b4e 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.54 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.1.53 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 3904572bd5..46ef3fd931 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.53", + "version": "0.1.54", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index 4b1ddff45c..d8c2573a0c 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,20 @@ # @tanstack/query-db-collection +## 1.1.0 + +### Minor Changes + +- Add top-level Query Collection support for additional Query observer options while preserving QueryClient defaultOptions behavior. ([#1665](https://github.com/TanStack/db/pull/1665)) + +### Patch Changes + +- Fix temporary query readiness listeners so subset unload and collection cleanup release them correctly during in-flight requests. ([#1673](https://github.com/TanStack/db/pull/1673)) + +- Extract internal query row ownership helpers to make lifecycle cleanup paths easier to reason about while preserving existing behavior. ([#1664](https://github.com/TanStack/db/pull/1664)) + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 1.0.48 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index 1b710c17d9..349070bb8f 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.48", + "version": "1.1.0", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index 82a036946e..339fab2c02 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.94 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.1.93 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index d39f14ef10..8906a0112b 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.93", + "version": "0.1.94", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index bd94edb877..d6f2259fe9 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.8 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.2.7 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index 4165791e16..e73a9a128d 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.7", + "version": "0.2.8", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 96660dac89..5080f21b69 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.82 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.1.81 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index 617a3964b2..ff8904e2ce 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.81", + "version": "0.1.82", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 148a36a6f8..ec19fb8fcc 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.30 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.2.29 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index c96fb373c7..55a4ef472b 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.29", + "version": "0.2.30", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 61b14dffc0..9efd9d8ff1 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.93 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.1.92 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index 18e4f6eb19..577b794daa 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.92", + "version": "0.1.93", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 37e9f05253..df41cc8def 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.8 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.8 + ## 0.2.7 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 6e6389e7c0..40aad5d512 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.20 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + - @tanstack/tauri-db-sqlite-persistence@0.2.8 + ## 0.0.19 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index c27eb64f56..8bb9140d75 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.19", + "version": "0.0.20", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index f741e9a966..9fe14ff63d 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.7", + "version": "0.2.8", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index 16643dbb0d..e88f3a7b0f 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.94 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.1.93 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 9435da8932..79f0e23456 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.93", + "version": "0.1.94", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 4331b7ba2c..db2efd02eb 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.127 + +### Patch Changes + +- Updated dependencies [[`8258d09`](https://github.com/TanStack/db/commit/8258d0955ab47c8510bd49ea59bcdbefd2ae054d), [`286964d`](https://github.com/TanStack/db/commit/286964d72612b59e3e427baabd9870f5a71a4281)]: + - @tanstack/db@0.6.16 + ## 0.0.126 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index 4a53013316..e0e9be53e1 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.126", + "version": "0.0.127", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9163601ea1..27ec7100cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.75 + specifier: ^0.1.76 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.15 + specifier: ^0.6.16 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.19 + specifier: ^0.1.20 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.7 + specifier: ^0.2.8 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.48 + specifier: ^1.1.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.15 + specifier: ^0.6.16 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.48 + specifier: ^1.1.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.7 + specifier: ^0.2.8 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.15 + specifier: ^0.6.16 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.13 + specifier: ^0.3.14 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.7 + specifier: ^0.2.8 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.7 + specifier: ^0.2.8 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.15 + specifier: ^0.6.16 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.48 + specifier: ^1.1.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.15 + specifier: ^0.6.16 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.48 + specifier: ^1.1.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.13 + specifier: ^0.3.14 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.48 + specifier: ^1.1.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.13 + specifier: ^0.3.14 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.48 + specifier: ^1.1.0 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.29 + specifier: ^0.2.30 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.93 + specifier: ^0.1.94 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 7f0fa36ff6d48b0494ed5f6a1223fd18c317e41f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:01:00 -0600 Subject: [PATCH 55/58] docs: regenerate API documentation (#1680) Co-authored-by: github-actions[bot] --- .../reference/functions/injectLiveQuery.md | 14 ++-- .../interfaces/InjectLiveQueryResult.md | 20 +++--- .../InjectLiveQueryResultWithCollection.md | 20 +++--- ...veQueryResultWithSingleResultCollection.md | 20 +++--- .../react/reference/functions/useLiveQuery.md | 16 ++--- .../type-aliases/UseLiveQueryStatus.md | 2 +- .../reference/functions/useLiveQuery.md | 10 +-- .../interfaces/UseLiveQueryReturn.md | 20 +++--- .../UseLiveQueryReturnWithCollection.md | 20 +++--- .../vue/reference/functions/useLiveQuery.md | 10 +-- .../interfaces/UseLiveQueryReturn.md | 20 +++--- .../UseLiveQueryReturnWithCollection.md | 20 +++--- ...veQueryReturnWithSingleResultCollection.md | 20 +++--- docs/reference/classes/BaseQueryBuilder.md | 10 +-- .../functions/electricCollectionOptions.md | 4 +- .../interfaces/ElectricCollectionConfig.md | 14 ++-- .../interfaces/ElectricCollectionUtils.md | 6 +- .../type-aliases/AwaitTxIdFn.md | 2 +- .../type-aliases/Txid.md | 2 +- .../functions/getLiveQueryStatusFlags.md | 26 +++++++ docs/reference/functions/hasVirtualProps.md | 2 +- docs/reference/functions/isCollection.md | 29 ++++++++ .../functions/isSingleResultCollection.md | 24 +++++++ docs/reference/index.md | 4 ++ .../interfaces/LiveQueryStatusFlags.md | 60 ++++++++++++++++ docs/reference/interfaces/Transaction.md | 37 ++++++---- docs/reference/interfaces/VirtualRowProps.md | 23 ++++--- .../functions/queryCollectionOptions.md | 8 +-- .../interfaces/QueryCollectionConfig.md | 69 +++++++++++++++---- .../interfaces/QueryCollectionUtils.md | 32 ++++----- .../ApplyJoinOptionalityToMergedSchema.md | 2 +- docs/reference/type-aliases/GetResult.md | 2 +- .../reference/type-aliases/InferResultType.md | 2 +- .../MergeContextForJoinCallback.md | 2 +- .../type-aliases/MergeContextWithJoinType.md | 2 +- docs/reference/type-aliases/Prettify.md | 2 +- docs/reference/type-aliases/Ref.md | 2 +- docs/reference/type-aliases/RefsForContext.md | 2 +- .../type-aliases/SchemaFromSource.md | 2 +- docs/reference/type-aliases/WithResult.md | 2 +- .../type-aliases/WithVirtualProps.md | 2 +- .../type-aliases/WithoutVirtualProps.md | 2 +- 42 files changed, 395 insertions(+), 193 deletions(-) create mode 100644 docs/reference/functions/getLiveQueryStatusFlags.md create mode 100644 docs/reference/functions/isCollection.md create mode 100644 docs/reference/functions/isSingleResultCollection.md create mode 100644 docs/reference/interfaces/LiveQueryStatusFlags.md diff --git a/docs/framework/angular/reference/functions/injectLiveQuery.md b/docs/framework/angular/reference/functions/injectLiveQuery.md index 5eb790a543..efe20157a4 100644 --- a/docs/framework/angular/reference/functions/injectLiveQuery.md +++ b/docs/framework/angular/reference/functions/injectLiveQuery.md @@ -11,7 +11,7 @@ title: injectLiveQuery function injectLiveQuery(options): InjectLiveQueryResult; ``` -Defined in: [index.ts:89](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L89) +Defined in: [index.ts:93](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L93) ### Type Parameters @@ -45,7 +45,7 @@ Defined in: [index.ts:89](https://github.com/TanStack/db/blob/main/packages/angu function injectLiveQuery(options): InjectLiveQueryResult; ``` -Defined in: [index.ts:99](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L99) +Defined in: [index.ts:103](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L103) ### Type Parameters @@ -79,7 +79,7 @@ Defined in: [index.ts:99](https://github.com/TanStack/db/blob/main/packages/angu function injectLiveQuery(queryFn): InjectLiveQueryResult; ``` -Defined in: [index.ts:109](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L109) +Defined in: [index.ts:113](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L113) ### Type Parameters @@ -103,7 +103,7 @@ Defined in: [index.ts:109](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(queryFn): InjectLiveQueryResult; ``` -Defined in: [index.ts:112](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L112) +Defined in: [index.ts:116](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L116) ### Type Parameters @@ -127,7 +127,7 @@ Defined in: [index.ts:112](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(config): InjectLiveQueryResult; ``` -Defined in: [index.ts:117](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L117) +Defined in: [index.ts:121](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L121) ### Type Parameters @@ -151,7 +151,7 @@ Defined in: [index.ts:117](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(liveQueryCollection): InjectLiveQueryResultWithCollection; ``` -Defined in: [index.ts:121](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L121) +Defined in: [index.ts:125](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L125) ### Type Parameters @@ -183,7 +183,7 @@ Defined in: [index.ts:121](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(liveQueryCollection): InjectLiveQueryResultWithSingleResultCollection; ``` -Defined in: [index.ts:129](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L129) +Defined in: [index.ts:133](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L133) ### Type Parameters diff --git a/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md b/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md index 066d6b5bab..cf4d8f3cee 100644 --- a/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md +++ b/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md @@ -5,7 +5,7 @@ title: InjectLiveQueryResult # Interface: InjectLiveQueryResult\ -Defined in: [index.ts:32](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L32) +Defined in: [index.ts:36](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L36) The result of calling `injectLiveQuery`. Contains reactive signals for the query state and data. @@ -27,7 +27,7 @@ collection: Signal< | null>; ``` -Defined in: [index.ts:38](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L38) +Defined in: [index.ts:42](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L42) A signal containing the underlying collection instance (null for disabled queries) @@ -39,7 +39,7 @@ A signal containing the underlying collection instance (null for disabled querie data: Signal>; ``` -Defined in: [index.ts:36](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L36) +Defined in: [index.ts:40](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L40) A signal containing the results as an array, or single result for findOne queries @@ -51,7 +51,7 @@ A signal containing the results as an array, or single result for findOne querie isCleanedUp: Signal; ``` -Defined in: [index.ts:54](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L54) +Defined in: [index.ts:58](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L58) A signal indicating whether the collection has been cleaned up @@ -63,7 +63,7 @@ A signal indicating whether the collection has been cleaned up isError: Signal; ``` -Defined in: [index.ts:52](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L52) +Defined in: [index.ts:56](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L56) A signal indicating whether the collection has an error @@ -75,7 +75,7 @@ A signal indicating whether the collection has an error isIdle: Signal; ``` -Defined in: [index.ts:50](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L50) +Defined in: [index.ts:54](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L54) A signal indicating whether the collection is idle @@ -87,7 +87,7 @@ A signal indicating whether the collection is idle isLoading: Signal; ``` -Defined in: [index.ts:46](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L46) +Defined in: [index.ts:50](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L50) A signal indicating whether the collection is currently loading @@ -99,7 +99,7 @@ A signal indicating whether the collection is currently loading isReady: Signal; ``` -Defined in: [index.ts:48](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L48) +Defined in: [index.ts:52](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L52) A signal indicating whether the collection is ready @@ -111,7 +111,7 @@ A signal indicating whether the collection is ready state: Signal[K] }>>; ``` -Defined in: [index.ts:34](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L34) +Defined in: [index.ts:38](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L38) A signal containing the complete state map of results keyed by their ID @@ -123,6 +123,6 @@ A signal containing the complete state map of results keyed by their ID status: Signal; ``` -Defined in: [index.ts:44](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L44) +Defined in: [index.ts:48](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L48) A signal containing the current status of the collection diff --git a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md index c6f1c6c3bc..06cefc8bc6 100644 --- a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md +++ b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md @@ -5,7 +5,7 @@ title: InjectLiveQueryResultWithCollection # Interface: InjectLiveQueryResultWithCollection\ -Defined in: [index.ts:57](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L57) +Defined in: [index.ts:61](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L61) ## Type Parameters @@ -32,7 +32,7 @@ collection: Signal< | null>; ``` -Defined in: [index.ts:64](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L64) +Defined in: [index.ts:68](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L68) *** @@ -42,7 +42,7 @@ Defined in: [index.ts:64](https://github.com/TanStack/db/blob/main/packages/angu data: Signal; ``` -Defined in: [index.ts:63](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L63) +Defined in: [index.ts:67](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L67) *** @@ -52,7 +52,7 @@ Defined in: [index.ts:63](https://github.com/TanStack/db/blob/main/packages/angu isCleanedUp: Signal; ``` -Defined in: [index.ts:70](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L70) +Defined in: [index.ts:74](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L74) *** @@ -62,7 +62,7 @@ Defined in: [index.ts:70](https://github.com/TanStack/db/blob/main/packages/angu isError: Signal; ``` -Defined in: [index.ts:69](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L69) +Defined in: [index.ts:73](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L73) *** @@ -72,7 +72,7 @@ Defined in: [index.ts:69](https://github.com/TanStack/db/blob/main/packages/angu isIdle: Signal; ``` -Defined in: [index.ts:68](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L68) +Defined in: [index.ts:72](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L72) *** @@ -82,7 +82,7 @@ Defined in: [index.ts:68](https://github.com/TanStack/db/blob/main/packages/angu isLoading: Signal; ``` -Defined in: [index.ts:66](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L66) +Defined in: [index.ts:70](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L70) *** @@ -92,7 +92,7 @@ Defined in: [index.ts:66](https://github.com/TanStack/db/blob/main/packages/angu isReady: Signal; ``` -Defined in: [index.ts:67](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L67) +Defined in: [index.ts:71](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L71) *** @@ -102,7 +102,7 @@ Defined in: [index.ts:67](https://github.com/TanStack/db/blob/main/packages/angu state: Signal>; ``` -Defined in: [index.ts:62](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L62) +Defined in: [index.ts:66](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L66) *** @@ -112,4 +112,4 @@ Defined in: [index.ts:62](https://github.com/TanStack/db/blob/main/packages/angu status: Signal; ``` -Defined in: [index.ts:65](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L65) +Defined in: [index.ts:69](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L69) diff --git a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md index 2cf88f2856..29daff2673 100644 --- a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md +++ b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md @@ -5,7 +5,7 @@ title: InjectLiveQueryResultWithSingleResultCollection # Interface: InjectLiveQueryResultWithSingleResultCollection\ -Defined in: [index.ts:73](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L73) +Defined in: [index.ts:77](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L77) ## Type Parameters @@ -32,7 +32,7 @@ collection: Signal< | null>; ``` -Defined in: [index.ts:80](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L80) +Defined in: [index.ts:84](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L84) *** @@ -42,7 +42,7 @@ Defined in: [index.ts:80](https://github.com/TanStack/db/blob/main/packages/angu data: Signal; ``` -Defined in: [index.ts:79](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L79) +Defined in: [index.ts:83](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L83) *** @@ -52,7 +52,7 @@ Defined in: [index.ts:79](https://github.com/TanStack/db/blob/main/packages/angu isCleanedUp: Signal; ``` -Defined in: [index.ts:86](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L86) +Defined in: [index.ts:90](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L90) *** @@ -62,7 +62,7 @@ Defined in: [index.ts:86](https://github.com/TanStack/db/blob/main/packages/angu isError: Signal; ``` -Defined in: [index.ts:85](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L85) +Defined in: [index.ts:89](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L89) *** @@ -72,7 +72,7 @@ Defined in: [index.ts:85](https://github.com/TanStack/db/blob/main/packages/angu isIdle: Signal; ``` -Defined in: [index.ts:84](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L84) +Defined in: [index.ts:88](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L88) *** @@ -82,7 +82,7 @@ Defined in: [index.ts:84](https://github.com/TanStack/db/blob/main/packages/angu isLoading: Signal; ``` -Defined in: [index.ts:82](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L82) +Defined in: [index.ts:86](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L86) *** @@ -92,7 +92,7 @@ Defined in: [index.ts:82](https://github.com/TanStack/db/blob/main/packages/angu isReady: Signal; ``` -Defined in: [index.ts:83](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L83) +Defined in: [index.ts:87](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L87) *** @@ -102,7 +102,7 @@ Defined in: [index.ts:83](https://github.com/TanStack/db/blob/main/packages/angu state: Signal>; ``` -Defined in: [index.ts:78](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L78) +Defined in: [index.ts:82](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L82) *** @@ -112,4 +112,4 @@ Defined in: [index.ts:78](https://github.com/TanStack/db/blob/main/packages/angu status: Signal; ``` -Defined in: [index.ts:81](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L81) +Defined in: [index.ts:85](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L85) diff --git a/docs/framework/react/reference/functions/useLiveQuery.md b/docs/framework/react/reference/functions/useLiveQuery.md index accc95e0fe..15c4a155b3 100644 --- a/docs/framework/react/reference/functions/useLiveQuery.md +++ b/docs/framework/react/reference/functions/useLiveQuery.md @@ -11,7 +11,7 @@ title: useLiveQuery function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:84](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L84) +Defined in: [useLiveQuery.ts:85](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L85) Create a live query using a query function @@ -168,7 +168,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:101](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L101) +Defined in: [useLiveQuery.ts:102](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L102) Create a live query using a query function @@ -329,7 +329,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:120](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L120) +Defined in: [useLiveQuery.ts:121](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L121) Create a live query using a query function @@ -493,7 +493,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:139](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L139) +Defined in: [useLiveQuery.ts:140](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L140) Create a live query using a query function @@ -662,7 +662,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:162](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L162) +Defined in: [useLiveQuery.ts:163](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L163) Create a live query using a query function @@ -842,7 +842,7 @@ return ( function useLiveQuery(config, deps?): object; ``` -Defined in: [useLiveQuery.ts:230](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L230) +Defined in: [useLiveQuery.ts:231](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L231) Create a live query using configuration object @@ -972,7 +972,7 @@ return

{data.length} items loaded
function useLiveQuery(liveQueryCollection): object; ``` -Defined in: [useLiveQuery.ts:276](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L276) +Defined in: [useLiveQuery.ts:277](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L277) Subscribe to an existing live query collection @@ -1100,7 +1100,7 @@ return
{data.map(item => )}
function useLiveQuery(liveQueryCollection): object; ``` -Defined in: [useLiveQuery.ts:296](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L296) +Defined in: [useLiveQuery.ts:297](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L297) Create a live query using a query function diff --git a/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md b/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md index 93bbcd634b..24c41b5bb1 100644 --- a/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md +++ b/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md @@ -9,4 +9,4 @@ title: UseLiveQueryStatus type UseLiveQueryStatus = CollectionStatus | "disabled"; ``` -Defined in: [useLiveQuery.ts:23](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L23) +Defined in: [useLiveQuery.ts:24](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L24) diff --git a/docs/framework/svelte/reference/functions/useLiveQuery.md b/docs/framework/svelte/reference/functions/useLiveQuery.md index 1ad0b46d1b..d251a36b7c 100644 --- a/docs/framework/svelte/reference/functions/useLiveQuery.md +++ b/docs/framework/svelte/reference/functions/useLiveQuery.md @@ -11,7 +11,7 @@ title: useLiveQuery function useLiveQuery(queryFn, deps?): UseLiveQueryReturn<{ [K in string | number | symbol]: ResultValue[K] }, InferResultType>; ``` -Defined in: [useLiveQuery.svelte.ts:160](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L160) +Defined in: [useLiveQuery.svelte.ts:164](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L164) Create a live query using a query function @@ -137,7 +137,7 @@ const todosQuery = useLiveQuery((q) => function useLiveQuery(queryFn, deps?): UseLiveQueryReturn<{ [K in string | number | symbol]: ResultValue[K] }, InferResultType | undefined>; ``` -Defined in: [useLiveQuery.svelte.ts:166](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L166) +Defined in: [useLiveQuery.svelte.ts:170](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L170) Create a live query using a query function @@ -263,7 +263,7 @@ const todosQuery = useLiveQuery((q) => function useLiveQuery(config, deps?): UseLiveQueryReturn<{ [K in string | number | symbol]: ResultValue[K] }, InferResultType>; ``` -Defined in: [useLiveQuery.svelte.ts:214](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L214) +Defined in: [useLiveQuery.svelte.ts:218](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L218) Create a live query using configuration object @@ -336,7 +336,7 @@ const itemsQuery = useLiveQuery({ function useLiveQuery(liveQueryCollection): UseLiveQueryReturnWithCollection; ``` -Defined in: [useLiveQuery.svelte.ts:263](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L263) +Defined in: [useLiveQuery.svelte.ts:267](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L267) Subscribe to an existing query collection (can be reactive) @@ -419,7 +419,7 @@ const queryResult = useLiveQuery(sharedQuery) function useLiveQuery(liveQueryCollection): UseLiveQueryReturnWithCollection; ``` -Defined in: [useLiveQuery.svelte.ts:274](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L274) +Defined in: [useLiveQuery.svelte.ts:278](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L278) Create a live query using a query function diff --git a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md index 714736f4bf..741971a118 100644 --- a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md +++ b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md @@ -5,7 +5,7 @@ title: UseLiveQueryReturn # Interface: UseLiveQueryReturn\ -Defined in: [useLiveQuery.svelte.ts:33](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L33) +Defined in: [useLiveQuery.svelte.ts:37](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L37) Return type for useLiveQuery hook @@ -28,7 +28,7 @@ collection: Collection; ``` -Defined in: [useLiveQuery.svelte.ts:36](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L36) +Defined in: [useLiveQuery.svelte.ts:40](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L40) The underlying query collection instance @@ -40,7 +40,7 @@ The underlying query collection instance data: TData; ``` -Defined in: [useLiveQuery.svelte.ts:35](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L35) +Defined in: [useLiveQuery.svelte.ts:39](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L39) Reactive array of query results in order, or single item when using findOne() @@ -52,7 +52,7 @@ Reactive array of query results in order, or single item when using findOne() isCleanedUp: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:42](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L42) +Defined in: [useLiveQuery.svelte.ts:46](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L46) True when query has been cleaned up @@ -64,7 +64,7 @@ True when query has been cleaned up isError: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:41](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L41) +Defined in: [useLiveQuery.svelte.ts:45](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L45) True when query encountered an error @@ -76,7 +76,7 @@ True when query encountered an error isIdle: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:40](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L40) +Defined in: [useLiveQuery.svelte.ts:44](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L44) True when query hasn't started yet @@ -88,7 +88,7 @@ True when query hasn't started yet isLoading: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:38](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L38) +Defined in: [useLiveQuery.svelte.ts:42](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L42) True while initial query data is loading @@ -100,7 +100,7 @@ True while initial query data is loading isReady: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:39](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L39) +Defined in: [useLiveQuery.svelte.ts:43](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L43) True when query has received first data and is ready @@ -112,7 +112,7 @@ True when query has received first data and is ready state: Map; ``` -Defined in: [useLiveQuery.svelte.ts:34](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L34) +Defined in: [useLiveQuery.svelte.ts:38](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L38) Reactive Map of query results (key → item) @@ -124,6 +124,6 @@ Reactive Map of query results (key → item) status: CollectionStatus; ``` -Defined in: [useLiveQuery.svelte.ts:37](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L37) +Defined in: [useLiveQuery.svelte.ts:41](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L41) Current query status diff --git a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md index 0d62c7ae82..916174515c 100644 --- a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md +++ b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md @@ -5,7 +5,7 @@ title: UseLiveQueryReturnWithCollection # Interface: UseLiveQueryReturnWithCollection\ -Defined in: [useLiveQuery.svelte.ts:45](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L45) +Defined in: [useLiveQuery.svelte.ts:49](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L49) ## Type Parameters @@ -33,7 +33,7 @@ Defined in: [useLiveQuery.svelte.ts:45](https://github.com/TanStack/db/blob/main collection: Collection; ``` -Defined in: [useLiveQuery.svelte.ts:53](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L53) +Defined in: [useLiveQuery.svelte.ts:57](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L57) *** @@ -43,7 +43,7 @@ Defined in: [useLiveQuery.svelte.ts:53](https://github.com/TanStack/db/blob/main data: TData; ``` -Defined in: [useLiveQuery.svelte.ts:52](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L52) +Defined in: [useLiveQuery.svelte.ts:56](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L56) *** @@ -53,7 +53,7 @@ Defined in: [useLiveQuery.svelte.ts:52](https://github.com/TanStack/db/blob/main isCleanedUp: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:59](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L59) +Defined in: [useLiveQuery.svelte.ts:63](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L63) *** @@ -63,7 +63,7 @@ Defined in: [useLiveQuery.svelte.ts:59](https://github.com/TanStack/db/blob/main isError: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:58](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L58) +Defined in: [useLiveQuery.svelte.ts:62](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L62) *** @@ -73,7 +73,7 @@ Defined in: [useLiveQuery.svelte.ts:58](https://github.com/TanStack/db/blob/main isIdle: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:57](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L57) +Defined in: [useLiveQuery.svelte.ts:61](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L61) *** @@ -83,7 +83,7 @@ Defined in: [useLiveQuery.svelte.ts:57](https://github.com/TanStack/db/blob/main isLoading: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:55](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L55) +Defined in: [useLiveQuery.svelte.ts:59](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L59) *** @@ -93,7 +93,7 @@ Defined in: [useLiveQuery.svelte.ts:55](https://github.com/TanStack/db/blob/main isReady: boolean; ``` -Defined in: [useLiveQuery.svelte.ts:56](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L56) +Defined in: [useLiveQuery.svelte.ts:60](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L60) *** @@ -103,7 +103,7 @@ Defined in: [useLiveQuery.svelte.ts:56](https://github.com/TanStack/db/blob/main state: Map; ``` -Defined in: [useLiveQuery.svelte.ts:51](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L51) +Defined in: [useLiveQuery.svelte.ts:55](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L55) *** @@ -113,4 +113,4 @@ Defined in: [useLiveQuery.svelte.ts:51](https://github.com/TanStack/db/blob/main status: CollectionStatus; ``` -Defined in: [useLiveQuery.svelte.ts:54](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L54) +Defined in: [useLiveQuery.svelte.ts:58](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L58) diff --git a/docs/framework/vue/reference/functions/useLiveQuery.md b/docs/framework/vue/reference/functions/useLiveQuery.md index 71c717e542..97b3f0bfee 100644 --- a/docs/framework/vue/reference/functions/useLiveQuery.md +++ b/docs/framework/vue/reference/functions/useLiveQuery.md @@ -11,7 +11,7 @@ title: useLiveQuery function useLiveQuery(queryFn, deps?): UseLiveQueryReturn; ``` -Defined in: [useLiveQuery.ts:134](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L134) +Defined in: [useLiveQuery.ts:137](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L137) Create a live query using a query function @@ -97,7 +97,7 @@ const { data, isLoading, isError, status } = useLiveQuery((q) => function useLiveQuery(queryFn, deps?): UseLiveQueryReturn; ``` -Defined in: [useLiveQuery.ts:140](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L140) +Defined in: [useLiveQuery.ts:143](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L143) Create a live query using a query function @@ -183,7 +183,7 @@ const { data, isLoading, isError, status } = useLiveQuery((q) => function useLiveQuery(config, deps?): UseLiveQueryReturn; ``` -Defined in: [useLiveQuery.ts:180](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L180) +Defined in: [useLiveQuery.ts:183](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L183) Create a live query using configuration object @@ -251,7 +251,7 @@ const { data, isLoading, isReady, isError } = useLiveQuery({ function useLiveQuery(liveQueryCollection): UseLiveQueryReturnWithCollection; ``` -Defined in: [useLiveQuery.ts:225](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L225) +Defined in: [useLiveQuery.ts:228](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L228) Subscribe to an existing query collection (can be reactive) @@ -330,7 +330,7 @@ const { data, isLoading, isError } = useLiveQuery(sharedQuery) function useLiveQuery(liveQueryCollection): UseLiveQueryReturnWithSingleResultCollection; ``` -Defined in: [useLiveQuery.ts:236](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L236) +Defined in: [useLiveQuery.ts:239](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L239) Create a live query using a query function diff --git a/docs/framework/vue/reference/interfaces/UseLiveQueryReturn.md b/docs/framework/vue/reference/interfaces/UseLiveQueryReturn.md index d434f46cd7..927376b10d 100644 --- a/docs/framework/vue/reference/interfaces/UseLiveQueryReturn.md +++ b/docs/framework/vue/reference/interfaces/UseLiveQueryReturn.md @@ -5,7 +5,7 @@ title: UseLiveQueryReturn # Interface: UseLiveQueryReturn\ -Defined in: [useLiveQuery.ts:40](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L40) +Defined in: [useLiveQuery.ts:43](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L43) Return type for useLiveQuery hook @@ -24,7 +24,7 @@ collection: ComputedRef, { [K in string | number | symbol]: ResultValue[K] }>>; ``` -Defined in: [useLiveQuery.ts:43](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L43) +Defined in: [useLiveQuery.ts:46](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L46) The underlying query collection instance @@ -36,7 +36,7 @@ The underlying query collection instance data: ComputedRef>; ``` -Defined in: [useLiveQuery.ts:42](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L42) +Defined in: [useLiveQuery.ts:45](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L45) Reactive array of query results in order, or single result for findOne queries @@ -48,7 +48,7 @@ Reactive array of query results in order, or single result for findOne queries isCleanedUp: ComputedRef; ``` -Defined in: [useLiveQuery.ts:49](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L49) +Defined in: [useLiveQuery.ts:52](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L52) True when query has been cleaned up @@ -60,7 +60,7 @@ True when query has been cleaned up isError: ComputedRef; ``` -Defined in: [useLiveQuery.ts:48](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L48) +Defined in: [useLiveQuery.ts:51](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L51) True when query encountered an error @@ -72,7 +72,7 @@ True when query encountered an error isIdle: ComputedRef; ``` -Defined in: [useLiveQuery.ts:47](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L47) +Defined in: [useLiveQuery.ts:50](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L50) True when query hasn't started yet @@ -84,7 +84,7 @@ True when query hasn't started yet isLoading: ComputedRef; ``` -Defined in: [useLiveQuery.ts:45](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L45) +Defined in: [useLiveQuery.ts:48](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L48) True while initial query data is loading @@ -96,7 +96,7 @@ True while initial query data is loading isReady: ComputedRef; ``` -Defined in: [useLiveQuery.ts:46](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L46) +Defined in: [useLiveQuery.ts:49](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L49) True when query has received first data and is ready @@ -108,7 +108,7 @@ True when query has received first data and is ready state: ComputedRef[K] }>>; ``` -Defined in: [useLiveQuery.ts:41](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L41) +Defined in: [useLiveQuery.ts:44](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L44) Reactive Map of query results (key → item) @@ -120,6 +120,6 @@ Reactive Map of query results (key → item) status: ComputedRef; ``` -Defined in: [useLiveQuery.ts:44](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L44) +Defined in: [useLiveQuery.ts:47](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L47) Current query status diff --git a/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithCollection.md b/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithCollection.md index 8f63d93141..b0a79b7d68 100644 --- a/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithCollection.md +++ b/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithCollection.md @@ -5,7 +5,7 @@ title: UseLiveQueryReturnWithCollection # Interface: UseLiveQueryReturnWithCollection\ -Defined in: [useLiveQuery.ts:52](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L52) +Defined in: [useLiveQuery.ts:55](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L55) ## Type Parameters @@ -29,7 +29,7 @@ Defined in: [useLiveQuery.ts:52](https://github.com/TanStack/db/blob/main/packag collection: ComputedRef, T>>; ``` -Defined in: [useLiveQuery.ts:59](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L59) +Defined in: [useLiveQuery.ts:62](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L62) *** @@ -39,7 +39,7 @@ Defined in: [useLiveQuery.ts:59](https://github.com/TanStack/db/blob/main/packag data: ComputedRef; ``` -Defined in: [useLiveQuery.ts:58](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L58) +Defined in: [useLiveQuery.ts:61](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L61) *** @@ -49,7 +49,7 @@ Defined in: [useLiveQuery.ts:58](https://github.com/TanStack/db/blob/main/packag isCleanedUp: ComputedRef; ``` -Defined in: [useLiveQuery.ts:65](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L65) +Defined in: [useLiveQuery.ts:68](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L68) *** @@ -59,7 +59,7 @@ Defined in: [useLiveQuery.ts:65](https://github.com/TanStack/db/blob/main/packag isError: ComputedRef; ``` -Defined in: [useLiveQuery.ts:64](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L64) +Defined in: [useLiveQuery.ts:67](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L67) *** @@ -69,7 +69,7 @@ Defined in: [useLiveQuery.ts:64](https://github.com/TanStack/db/blob/main/packag isIdle: ComputedRef; ``` -Defined in: [useLiveQuery.ts:63](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L63) +Defined in: [useLiveQuery.ts:66](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L66) *** @@ -79,7 +79,7 @@ Defined in: [useLiveQuery.ts:63](https://github.com/TanStack/db/blob/main/packag isLoading: ComputedRef; ``` -Defined in: [useLiveQuery.ts:61](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L61) +Defined in: [useLiveQuery.ts:64](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L64) *** @@ -89,7 +89,7 @@ Defined in: [useLiveQuery.ts:61](https://github.com/TanStack/db/blob/main/packag isReady: ComputedRef; ``` -Defined in: [useLiveQuery.ts:62](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L62) +Defined in: [useLiveQuery.ts:65](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L65) *** @@ -99,7 +99,7 @@ Defined in: [useLiveQuery.ts:62](https://github.com/TanStack/db/blob/main/packag state: ComputedRef>; ``` -Defined in: [useLiveQuery.ts:57](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L57) +Defined in: [useLiveQuery.ts:60](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L60) *** @@ -109,4 +109,4 @@ Defined in: [useLiveQuery.ts:57](https://github.com/TanStack/db/blob/main/packag status: ComputedRef; ``` -Defined in: [useLiveQuery.ts:60](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L60) +Defined in: [useLiveQuery.ts:63](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L63) diff --git a/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithSingleResultCollection.md b/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithSingleResultCollection.md index 5b9502a979..98fb29f150 100644 --- a/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithSingleResultCollection.md +++ b/docs/framework/vue/reference/interfaces/UseLiveQueryReturnWithSingleResultCollection.md @@ -5,7 +5,7 @@ title: UseLiveQueryReturnWithSingleResultCollection # Interface: UseLiveQueryReturnWithSingleResultCollection\ -Defined in: [useLiveQuery.ts:68](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L68) +Defined in: [useLiveQuery.ts:71](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L71) ## Type Parameters @@ -29,7 +29,7 @@ Defined in: [useLiveQuery.ts:68](https://github.com/TanStack/db/blob/main/packag collection: ComputedRef, T> & SingleResult>; ``` -Defined in: [useLiveQuery.ts:75](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L75) +Defined in: [useLiveQuery.ts:78](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L78) *** @@ -39,7 +39,7 @@ Defined in: [useLiveQuery.ts:75](https://github.com/TanStack/db/blob/main/packag data: ComputedRef; ``` -Defined in: [useLiveQuery.ts:74](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L74) +Defined in: [useLiveQuery.ts:77](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L77) *** @@ -49,7 +49,7 @@ Defined in: [useLiveQuery.ts:74](https://github.com/TanStack/db/blob/main/packag isCleanedUp: ComputedRef; ``` -Defined in: [useLiveQuery.ts:81](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L81) +Defined in: [useLiveQuery.ts:84](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L84) *** @@ -59,7 +59,7 @@ Defined in: [useLiveQuery.ts:81](https://github.com/TanStack/db/blob/main/packag isError: ComputedRef; ``` -Defined in: [useLiveQuery.ts:80](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L80) +Defined in: [useLiveQuery.ts:83](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L83) *** @@ -69,7 +69,7 @@ Defined in: [useLiveQuery.ts:80](https://github.com/TanStack/db/blob/main/packag isIdle: ComputedRef; ``` -Defined in: [useLiveQuery.ts:79](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L79) +Defined in: [useLiveQuery.ts:82](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L82) *** @@ -79,7 +79,7 @@ Defined in: [useLiveQuery.ts:79](https://github.com/TanStack/db/blob/main/packag isLoading: ComputedRef; ``` -Defined in: [useLiveQuery.ts:77](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L77) +Defined in: [useLiveQuery.ts:80](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L80) *** @@ -89,7 +89,7 @@ Defined in: [useLiveQuery.ts:77](https://github.com/TanStack/db/blob/main/packag isReady: ComputedRef; ``` -Defined in: [useLiveQuery.ts:78](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L78) +Defined in: [useLiveQuery.ts:81](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L81) *** @@ -99,7 +99,7 @@ Defined in: [useLiveQuery.ts:78](https://github.com/TanStack/db/blob/main/packag state: ComputedRef>; ``` -Defined in: [useLiveQuery.ts:73](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L73) +Defined in: [useLiveQuery.ts:76](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L76) *** @@ -109,4 +109,4 @@ Defined in: [useLiveQuery.ts:73](https://github.com/TanStack/db/blob/main/packag status: ComputedRef; ``` -Defined in: [useLiveQuery.ts:76](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L76) +Defined in: [useLiveQuery.ts:79](https://github.com/TanStack/db/blob/main/packages/vue-db/src/useLiveQuery.ts#L79) diff --git a/docs/reference/classes/BaseQueryBuilder.md b/docs/reference/classes/BaseQueryBuilder.md index 6e8842faf3..05dfc08521 100644 --- a/docs/reference/classes/BaseQueryBuilder.md +++ b/docs/reference/classes/BaseQueryBuilder.md @@ -310,7 +310,7 @@ An object with a single key-value pair where the key is the table alias and the ##### onCallback -[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? \{ \[K in string \| number \| symbol\]: ResultValue\\[K\] \} : never \}\[K\] \}\>\> +[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? ResultValue\ : never \}\[K\] \}\>\> A function that receives table references and returns the join condition @@ -455,7 +455,7 @@ An object with a single key-value pair where the key is the table alias and the ##### onCallback -[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? \{ \[K in string \| number \| symbol\]: ResultValue\\[K\] \} : never \}\[K\] \}\>\> +[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? ResultValue\ : never \}\[K\] \}\>\> A function that receives table references and returns the join condition @@ -509,7 +509,7 @@ An object with a single key-value pair where the key is the table alias and the ##### onCallback -[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? \{ \[K in string \| number \| symbol\]: ResultValue\\[K\] \} : never \}\[K\] \}\>\> +[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? ResultValue\ : never \}\[K\] \}\>\> A function that receives table references and returns the join condition @@ -573,7 +573,7 @@ An object with a single key-value pair where the key is the table alias and the ##### onCallback -[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? \{ \[K in string \| number \| symbol\]: ResultValue\\[K\] \} : never \}\[K\] \}\>\> +[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? ResultValue\ : never \}\[K\] \}\>\> A function that receives table references and returns the join condition @@ -745,7 +745,7 @@ An object with a single key-value pair where the key is the table alias and the ##### onCallback -[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? \{ \[K in string \| number \| symbol\]: ResultValue\\[K\] \} : never \}\[K\] \}\>\> +[`JoinOnCallback`](../type-aliases/JoinOnCallback.md)\<[`MergeContextForJoinCallback`](../type-aliases/MergeContextForJoinCallback.md)\<`TContext`, \{ \[K in string \| number \| symbol\]: \{ \[K in string \| number \| symbol\]: TSource\[K\] extends CollectionImpl\ ? InferCollectionType\ : TSource\[K\] extends QueryBuilder\ ? ResultValue\ : never \}\[K\] \}\>\> A function that receives table references and returns the join condition diff --git a/docs/reference/electric-db-collection/functions/electricCollectionOptions.md b/docs/reference/electric-db-collection/functions/electricCollectionOptions.md index 8e80aeed8e..a89acd634d 100644 --- a/docs/reference/electric-db-collection/functions/electricCollectionOptions.md +++ b/docs/reference/electric-db-collection/functions/electricCollectionOptions.md @@ -11,7 +11,7 @@ title: electricCollectionOptions function electricCollectionOptions(config): Omit, string | number, T, UtilsRecord>, "utils" | "onInsert" | "onUpdate" | "onDelete"> & Pick, T>, "onInsert" | "onUpdate" | "onDelete"> & object; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:576](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L576) +Defined in: [packages/electric-db-collection/src/electric.ts:592](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L592) Creates Electric collection options for use with a standard Collection @@ -43,7 +43,7 @@ Collection options with utilities function electricCollectionOptions(config): Omit, "utils" | "onInsert" | "onUpdate" | "onDelete"> & Pick, "onInsert" | "onUpdate" | "onDelete"> & object; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:594](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L594) +Defined in: [packages/electric-db-collection/src/electric.ts:610](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L610) Creates Electric collection options for use with a standard Collection diff --git a/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md b/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md index 95c0ec9f3c..32b7c1935d 100644 --- a/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md +++ b/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md @@ -5,7 +5,7 @@ title: ElectricCollectionConfig # Interface: ElectricCollectionConfig\ -Defined in: [packages/electric-db-collection/src/electric.ts:148](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L148) +Defined in: [packages/electric-db-collection/src/electric.ts:150](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L150) Configuration interface for Electric collection options @@ -35,7 +35,7 @@ The schema type for validation optional [ELECTRIC_TEST_HOOKS]: ElectricTestHooks; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:171](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L171) +Defined in: [packages/electric-db-collection/src/electric.ts:173](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L173) Internal test hooks (for testing only) Hidden via Symbol to prevent accidental usage in production @@ -48,7 +48,7 @@ Hidden via Symbol to prevent accidental usage in production optional onDelete: (params) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:288](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L288) +Defined in: [packages/electric-db-collection/src/electric.ts:290](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L290) Optional asynchronous handler function called before a delete operation @@ -100,7 +100,7 @@ onDelete: async ({ transaction, collection }) => { optional onInsert: (params) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:219](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L219) +Defined in: [packages/electric-db-collection/src/electric.ts:221](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L221) Optional asynchronous handler function called before an insert operation @@ -174,7 +174,7 @@ onInsert: async ({ transaction, collection }) => { optional onUpdate: (params) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:254](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L254) +Defined in: [packages/electric-db-collection/src/electric.ts:256](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L256) Optional asynchronous handler function called before an update operation @@ -227,7 +227,7 @@ onUpdate: async ({ transaction, collection }) => { shapeOptions: ShapeStreamOptions>; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:164](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L164) +Defined in: [packages/electric-db-collection/src/electric.ts:166](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L166) Configuration options for the ElectricSQL ShapeStream @@ -239,4 +239,4 @@ Configuration options for the ElectricSQL ShapeStream optional syncMode: ElectricSyncMode; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:165](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L165) +Defined in: [packages/electric-db-collection/src/electric.ts:167](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L167) diff --git a/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md b/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md index b7ce5f0c9c..7ef911d08f 100644 --- a/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md +++ b/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md @@ -5,7 +5,7 @@ title: ElectricCollectionUtils # Interface: ElectricCollectionUtils\ -Defined in: [packages/electric-db-collection/src/electric.ts:558](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L558) +Defined in: [packages/electric-db-collection/src/electric.ts:574](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L574) Electric collection utilities type @@ -33,7 +33,7 @@ Electric collection utilities type awaitMatch: AwaitMatchFn; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:562](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L562) +Defined in: [packages/electric-db-collection/src/electric.ts:578](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L578) *** @@ -43,4 +43,4 @@ Defined in: [packages/electric-db-collection/src/electric.ts:562](https://github awaitTxId: AwaitTxIdFn; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:561](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L561) +Defined in: [packages/electric-db-collection/src/electric.ts:577](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L577) diff --git a/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md b/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md index b03f81a358..f4d5ca8d58 100644 --- a/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md +++ b/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md @@ -9,7 +9,7 @@ title: AwaitTxIdFn type AwaitTxIdFn = (txId, timeout?) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:545](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L545) +Defined in: [packages/electric-db-collection/src/electric.ts:561](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L561) Type for the awaitTxId utility function diff --git a/docs/reference/electric-db-collection/type-aliases/Txid.md b/docs/reference/electric-db-collection/type-aliases/Txid.md index f16e8e37ad..02dc695ec1 100644 --- a/docs/reference/electric-db-collection/type-aliases/Txid.md +++ b/docs/reference/electric-db-collection/type-aliases/Txid.md @@ -9,6 +9,6 @@ title: Txid type Txid = number; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:86](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L86) +Defined in: [packages/electric-db-collection/src/electric.ts:88](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L88) Type representing a transaction ID in ElectricSQL diff --git a/docs/reference/functions/getLiveQueryStatusFlags.md b/docs/reference/functions/getLiveQueryStatusFlags.md new file mode 100644 index 0000000000..4dedb0d47f --- /dev/null +++ b/docs/reference/functions/getLiveQueryStatusFlags.md @@ -0,0 +1,26 @@ +--- +id: getLiveQueryStatusFlags +title: getLiveQueryStatusFlags +--- + +# Function: getLiveQueryStatusFlags() + +```ts +function getLiveQueryStatusFlags(status): LiveQueryStatusFlags; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L58) + +Derive the boolean status flags from a collection status. Adapters represent +a disabled query separately (with `isReady: true`); this covers the real +`CollectionStatus` values. + +## Parameters + +### status + +[`CollectionStatus`](../type-aliases/CollectionStatus.md) + +## Returns + +[`LiveQueryStatusFlags`](../interfaces/LiveQueryStatusFlags.md) diff --git a/docs/reference/functions/hasVirtualProps.md b/docs/reference/functions/hasVirtualProps.md index 57a2b9c614..19013aac4e 100644 --- a/docs/reference/functions/hasVirtualProps.md +++ b/docs/reference/functions/hasVirtualProps.md @@ -9,7 +9,7 @@ title: hasVirtualProps function hasVirtualProps(value): value is VirtualRowProps; ``` -Defined in: [packages/db/src/virtual-props.ts:145](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L145) +Defined in: [packages/db/src/virtual-props.ts:150](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L150) Checks if a value has virtual properties attached. diff --git a/docs/reference/functions/isCollection.md b/docs/reference/functions/isCollection.md new file mode 100644 index 0000000000..a846ecfa33 --- /dev/null +++ b/docs/reference/functions/isCollection.md @@ -0,0 +1,29 @@ +--- +id: isCollection +title: isCollection +--- + +# Function: isCollection() + +```ts +function isCollection(value): value is Collection, any>; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:22](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L22) + +Structural check for a live-query/`Collection` instance. + +Uses duck typing rather than `instanceof CollectionImpl` on purpose: adapters +and core can resolve to different copies of `@tanstack/db` (dual-package / +multi-realm), where `instanceof` gives false negatives. The three methods +below uniquely identify a Collection. + +## Parameters + +### value + +`unknown` + +## Returns + +`value is Collection, any>` diff --git a/docs/reference/functions/isSingleResultCollection.md b/docs/reference/functions/isSingleResultCollection.md new file mode 100644 index 0000000000..1ec948a6d7 --- /dev/null +++ b/docs/reference/functions/isSingleResultCollection.md @@ -0,0 +1,24 @@ +--- +id: isSingleResultCollection +title: isSingleResultCollection +--- + +# Function: isSingleResultCollection() + +```ts +function isSingleResultCollection(collection): boolean; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:35](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L35) + +Whether a collection yields a single result (`findOne`) rather than an array. + +## Parameters + +### collection + +[`Collection`](../interfaces/Collection.md)\<`any`, `any`, `any`\> + +## Returns + +`boolean` diff --git a/docs/reference/index.md b/docs/reference/index.md index eddfb1cb06..7030d02310 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -134,6 +134,7 @@ title: "@tanstack/db" - [IndexSuggestion](interfaces/IndexSuggestion.md) - [InsertConfig](interfaces/InsertConfig.md) - [LiveQueryCollectionConfig](interfaces/LiveQueryCollectionConfig.md) +- [LiveQueryStatusFlags](interfaces/LiveQueryStatusFlags.md) - [LocalOnlyCollectionConfig](interfaces/LocalOnlyCollectionConfig.md) - [LocalOnlyCollectionUtils](interfaces/LocalOnlyCollectionUtils.md) - [LocalStorageCollectionConfig](interfaces/LocalStorageCollectionConfig.md) @@ -298,18 +299,21 @@ title: "@tanstack/db" - [findIndexForField](functions/findIndexForField.md) - [getActiveTransaction](functions/getActiveTransaction.md) - [getIndexDevModeConfig](functions/getIndexDevModeConfig.md) +- [getLiveQueryStatusFlags](functions/getLiveQueryStatusFlags.md) - [getQueryPatterns](functions/getQueryPatterns.md) - [gt](functions/gt.md) - [gte](functions/gte.md) - [hasVirtualProps](functions/hasVirtualProps.md) - [ilike](functions/ilike.md) - [inArray](functions/inArray.md) +- [isCollection](functions/isCollection.md) - [isDevModeEnabled](functions/isDevModeEnabled.md) - [isLimitSubset](functions/isLimitSubset.md) - [isNull](functions/isNull.md) - [isOffsetLimitSubset](functions/isOffsetLimitSubset.md) - [isOrderBySubset](functions/isOrderBySubset.md) - [isPredicateSubset](functions/isPredicateSubset.md) +- [isSingleResultCollection](functions/isSingleResultCollection.md) - [isUndefined](functions/isUndefined.md) - [isWhereSubset](functions/isWhereSubset.md) - [length](functions/length.md) diff --git a/docs/reference/interfaces/LiveQueryStatusFlags.md b/docs/reference/interfaces/LiveQueryStatusFlags.md new file mode 100644 index 0000000000..e687c972b0 --- /dev/null +++ b/docs/reference/interfaces/LiveQueryStatusFlags.md @@ -0,0 +1,60 @@ +--- +id: LiveQueryStatusFlags +title: LiveQueryStatusFlags +--- + +# Interface: LiveQueryStatusFlags + +Defined in: [packages/db/src/live-query-adapter.ts:45](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L45) + +The derived boolean status flags every adapter exposes for a query. + +## Properties + +### isCleanedUp + +```ts +isCleanedUp: boolean; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:50](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L50) + +*** + +### isError + +```ts +isError: boolean; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L49) + +*** + +### isIdle + +```ts +isIdle: boolean; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:48](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L48) + +*** + +### isLoading + +```ts +isLoading: boolean; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:46](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L46) + +*** + +### isReady + +```ts +isReady: boolean; +``` + +Defined in: [packages/db/src/live-query-adapter.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L47) diff --git a/docs/reference/interfaces/Transaction.md b/docs/reference/interfaces/Transaction.md index 38faa9ccac..6621aa3c91 100644 --- a/docs/reference/interfaces/Transaction.md +++ b/docs/reference/interfaces/Transaction.md @@ -21,7 +21,7 @@ Defined in: [packages/db/src/transactions.ts:209](https://github.com/TanStack/db autoCommit: boolean; ``` -Defined in: [packages/db/src/transactions.ts:215](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L215) +Defined in: [packages/db/src/transactions.ts:227](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L227) *** @@ -31,7 +31,7 @@ Defined in: [packages/db/src/transactions.ts:215](https://github.com/TanStack/db createdAt: Date; ``` -Defined in: [packages/db/src/transactions.ts:216](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L216) +Defined in: [packages/db/src/transactions.ts:228](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L228) *** @@ -41,7 +41,7 @@ Defined in: [packages/db/src/transactions.ts:216](https://github.com/TanStack/db optional error: object; ``` -Defined in: [packages/db/src/transactions.ts:219](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L219) +Defined in: [packages/db/src/transactions.ts:231](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L231) #### error @@ -73,7 +73,18 @@ Defined in: [packages/db/src/transactions.ts:210](https://github.com/TanStack/db isPersisted: Deferred>; ``` -Defined in: [packages/db/src/transactions.ts:214](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L214) +Defined in: [packages/db/src/transactions.ts:226](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L226) + +Deferred that settles when this transaction settles. + +Await `isPersisted.promise`, not `isPersisted` itself. The promise resolves +when the transaction completes successfully and rejects if the transaction +fails or is rolled back. + +For non-empty commits, the mutation function is the normal settlement +boundary. This does not inherently prove that a backend has uploaded, +confirmed, or read back the write unless the mutation function waits for +that backend observation before returning. *** @@ -83,7 +94,7 @@ Defined in: [packages/db/src/transactions.ts:214](https://github.com/TanStack/db metadata: Record; ``` -Defined in: [packages/db/src/transactions.ts:218](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L218) +Defined in: [packages/db/src/transactions.ts:230](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L230) *** @@ -113,7 +124,7 @@ Defined in: [packages/db/src/transactions.ts:213](https://github.com/TanStack/db sequenceNumber: number; ``` -Defined in: [packages/db/src/transactions.ts:217](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L217) +Defined in: [packages/db/src/transactions.ts:229](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L229) *** @@ -133,7 +144,7 @@ Defined in: [packages/db/src/transactions.ts:211](https://github.com/TanStack/db applyMutations(mutations): void; ``` -Defined in: [packages/db/src/transactions.ts:336](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L336) +Defined in: [packages/db/src/transactions.ts:348](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L348) Apply new mutations to this transaction, intelligently merging with existing mutations @@ -169,7 +180,7 @@ Array of new mutations to apply commit(): Promise>; ``` -Defined in: [packages/db/src/transactions.ts:493](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L493) +Defined in: [packages/db/src/transactions.ts:505](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L505) Commit the transaction and execute the mutation function @@ -228,7 +239,7 @@ console.log(tx.state) // "completed" or "failed" compareCreatedAt(other): number; ``` -Defined in: [packages/db/src/transactions.ts:547](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L547) +Defined in: [packages/db/src/transactions.ts:559](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L559) Compare two transactions by their createdAt time and sequence number in order to sort them in the order they were created. @@ -255,7 +266,7 @@ The other transaction to compare to mutate(callback): Transaction; ``` -Defined in: [packages/db/src/transactions.ts:296](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L296) +Defined in: [packages/db/src/transactions.ts:308](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L308) Execute collection operations within this transaction @@ -331,7 +342,7 @@ await tx.commit() rollback(config?): Transaction; ``` -Defined in: [packages/db/src/transactions.ts:410](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L410) +Defined in: [packages/db/src/transactions.ts:422](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L422) Rollback the transaction and any conflicting transactions @@ -398,7 +409,7 @@ try { setState(newState): void; ``` -Defined in: [packages/db/src/transactions.ts:239](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L239) +Defined in: [packages/db/src/transactions.ts:251](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L251) #### Parameters @@ -418,7 +429,7 @@ Defined in: [packages/db/src/transactions.ts:239](https://github.com/TanStack/db touchCollection(): void; ``` -Defined in: [packages/db/src/transactions.ts:438](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L438) +Defined in: [packages/db/src/transactions.ts:450](https://github.com/TanStack/db/blob/main/packages/db/src/transactions.ts#L450) #### Returns diff --git a/docs/reference/interfaces/VirtualRowProps.md b/docs/reference/interfaces/VirtualRowProps.md index c4719d188e..63d2ca95c5 100644 --- a/docs/reference/interfaces/VirtualRowProps.md +++ b/docs/reference/interfaces/VirtualRowProps.md @@ -21,7 +21,7 @@ These properties are: // Accessing virtual properties on a row const user = collection.get('user-1') if (user.$synced) { - console.log('Confirmed by backend') + console.log('No pending local optimistic writes for this row') } if (user.$origin === 'local') { console.log('Created/modified locally') @@ -30,7 +30,7 @@ if (user.$origin === 'local') { ```typescript // Using virtual properties in queries -const confirmedOrders = createLiveQueryCollection({ +const ordersWithoutLocalWrites = createLiveQueryCollection({ query: (q) => q .from({ order: orders }) .where(({ order }) => eq(order.$synced, true)) @@ -53,7 +53,7 @@ The type of the row's key (string or number) readonly $collectionId: string; ``` -Defined in: [packages/db/src/virtual-props.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L96) +Defined in: [packages/db/src/virtual-props.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L101) The ID of the source collection this row originated from. @@ -68,7 +68,7 @@ For live query collections, this is the ID of the upstream collection. readonly $key: TKey; ``` -Defined in: [packages/db/src/virtual-props.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L88) +Defined in: [packages/db/src/virtual-props.ts:93](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L93) The row's key (primary identifier). @@ -83,7 +83,7 @@ Useful when you need the key in projections or computations. readonly $origin: VirtualOrigin; ``` -Defined in: [packages/db/src/virtual-props.ts:80](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L80) +Defined in: [packages/db/src/virtual-props.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L85) Origin of the last confirmed change to this row, from the current client's perspective. @@ -101,12 +101,17 @@ For live query collections, this is passed through from the source collection. readonly $synced: boolean; ``` -Defined in: [packages/db/src/virtual-props.ts:69](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L69) +Defined in: [packages/db/src/virtual-props.ts:74](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L74) -Whether this row reflects confirmed state from the backend. +Whether this row currently has no pending local optimistic writes. -- `true`: Row is confirmed by the backend (no pending optimistic mutations) -- `false`: Row has pending optimistic mutations that haven't been confirmed +- `true`: No pending local optimistic mutation currently affects this row +- `false`: One or more pending local optimistic mutations currently affect this row + +This is local mutation status. It does not prove that a backend has uploaded, +confirmed, or read back the row. If you need backend-confirmed status, keep +your mutation function pending until that backend observation has happened, +or expose adapter-specific status. For local-only collections (no sync), this is always `true`. For live query collections, this is passed through from the source collection. diff --git a/docs/reference/query-db-collection/functions/queryCollectionOptions.md b/docs/reference/query-db-collection/functions/queryCollectionOptions.md index 1e3a3e539c..5da712517b 100644 --- a/docs/reference/query-db-collection/functions/queryCollectionOptions.md +++ b/docs/reference/query-db-collection/functions/queryCollectionOptions.md @@ -11,7 +11,7 @@ title: queryCollectionOptions function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:445](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L445) +Defined in: [packages/query-db-collection/src/query.ts:510](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L510) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -151,7 +151,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:480](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L480) +Defined in: [packages/query-db-collection/src/query.ts:545](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L545) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -291,7 +291,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:513](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L513) +Defined in: [packages/query-db-collection/src/query.ts:578](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L578) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -423,7 +423,7 @@ const todosCollection = createCollection( function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:547](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L547) +Defined in: [packages/query-db-collection/src/query.ts:612](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L612) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. diff --git a/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md b/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md index 1888ec8ebd..bbe7bf8f69 100644 --- a/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md +++ b/docs/reference/query-db-collection/interfaces/QueryCollectionConfig.md @@ -5,7 +5,7 @@ title: QueryCollectionConfig # Interface: QueryCollectionConfig\ -Defined in: [packages/query-db-collection/src/query.ts:61](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L61) +Defined in: [packages/query-db-collection/src/query.ts:95](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L95) Configuration options for creating a Query Collection @@ -63,7 +63,7 @@ The schema type for validation optional enabled: Enabled; ``` -Defined in: [packages/query-db-collection/src/query.ts:87](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L87) +Defined in: [packages/query-db-collection/src/query.ts:124](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L124) Whether the query should automatically run (default: true) @@ -75,7 +75,7 @@ Whether the query should automatically run (default: true) optional gcTime: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:122](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L122) +Defined in: [packages/query-db-collection/src/query.ts:159](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L159) Time in milliseconds after which the collection will be garbage collected when it has no active subscribers. Defaults to 5 minutes (300000ms). @@ -94,7 +94,7 @@ BaseCollectionConfig.gcTime optional meta: Record; ``` -Defined in: [packages/query-db-collection/src/query.ts:151](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L151) +Defined in: [packages/query-db-collection/src/query.ts:216](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L216) Metadata to pass to the query. Available in queryFn via context.meta @@ -120,13 +120,23 @@ meta: { *** +### networkMode? + +```ts +optional networkMode: NetworkMode; +``` + +Defined in: [packages/query-db-collection/src/query.ts:187](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L187) + +*** + ### persistedGcTime? ```ts optional persistedGcTime: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:129](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L129) +Defined in: [packages/query-db-collection/src/query.ts:194](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L194) *** @@ -136,7 +146,7 @@ Defined in: [packages/query-db-collection/src/query.ts:129](https://github.com/T queryClient: QueryClient; ``` -Defined in: [packages/query-db-collection/src/query.ts:83](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L83) +Defined in: [packages/query-db-collection/src/query.ts:120](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L120) The TanStack Query client instance @@ -148,7 +158,7 @@ The TanStack Query client instance queryFn: TQueryFn extends (context) => any[] | Promise ? (context) => T[] | Promise : TQueryFn; ``` -Defined in: [packages/query-db-collection/src/query.ts:75](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L75) +Defined in: [packages/query-db-collection/src/query.ts:109](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L109) Function that fetches data from the server. Must return the complete collection state @@ -160,7 +170,7 @@ Function that fetches data from the server. Must return the complete collection queryKey: TQueryKey | TQueryKeyBuilder; ``` -Defined in: [packages/query-db-collection/src/query.ts:73](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L73) +Defined in: [packages/query-db-collection/src/query.ts:107](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L107) The query key used by TanStack Query to identify this query @@ -172,7 +182,37 @@ The query key used by TanStack Query to identify this query optional refetchInterval: number | false | (query) => number | false | undefined; ``` -Defined in: [packages/query-db-collection/src/query.ts:94](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L94) +Defined in: [packages/query-db-collection/src/query.ts:131](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L131) + +*** + +### refetchOnMount? + +```ts +optional refetchOnMount: boolean | "always" | (query) => boolean | "always"; +``` + +Defined in: [packages/query-db-collection/src/query.ts:180](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L180) + +*** + +### refetchOnReconnect? + +```ts +optional refetchOnReconnect: boolean | "always" | (query) => boolean | "always"; +``` + +Defined in: [packages/query-db-collection/src/query.ts:173](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L173) + +*** + +### refetchOnWindowFocus? + +```ts +optional refetchOnWindowFocus: boolean | "always" | (query) => boolean | "always"; +``` + +Defined in: [packages/query-db-collection/src/query.ts:166](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L166) *** @@ -182,7 +222,7 @@ Defined in: [packages/query-db-collection/src/query.ts:94](https://github.com/Ta optional retry: RetryValue; ``` -Defined in: [packages/query-db-collection/src/query.ts:101](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L101) +Defined in: [packages/query-db-collection/src/query.ts:138](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L138) *** @@ -192,7 +232,7 @@ Defined in: [packages/query-db-collection/src/query.ts:101](https://github.com/T optional retryDelay: RetryDelayValue; ``` -Defined in: [packages/query-db-collection/src/query.ts:108](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L108) +Defined in: [packages/query-db-collection/src/query.ts:145](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L145) *** @@ -202,7 +242,10 @@ Defined in: [packages/query-db-collection/src/query.ts:108](https://github.com/T optional select: (data) => T[]; ``` -Defined in: [packages/query-db-collection/src/query.ts:81](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L81) +Defined in: [packages/query-db-collection/src/query.ts:118](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L118) + +Extracts the row array TanStack DB materializes from the Query response. +The Query cache keeps the original response shape. #### Parameters @@ -222,4 +265,4 @@ Defined in: [packages/query-db-collection/src/query.ts:81](https://github.com/Ta optional staleTime: StaleTimeFunction; ``` -Defined in: [packages/query-db-collection/src/query.ts:115](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L115) +Defined in: [packages/query-db-collection/src/query.ts:152](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L152) diff --git a/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md b/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md index 88bb9c88eb..b2ab5a7c36 100644 --- a/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md +++ b/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md @@ -5,7 +5,7 @@ title: QueryCollectionUtils # Interface: QueryCollectionUtils\ -Defined in: [packages/query-db-collection/src/query.ts:170](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L170) +Defined in: [packages/query-db-collection/src/query.ts:235](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L235) Utility methods available on Query Collections for direct writes and manual operations. Direct writes bypass the normal query/mutation flow and write directly to the synced data store. @@ -54,7 +54,7 @@ The type of errors that can occur during queries clearError: () => Promise; ``` -Defined in: [packages/query-db-collection/src/query.ts:215](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L215) +Defined in: [packages/query-db-collection/src/query.ts:280](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L280) Clear the error state and trigger a refetch of the query @@ -76,7 +76,7 @@ Error if the refetch fails dataUpdatedAt: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:206](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L206) +Defined in: [packages/query-db-collection/src/query.ts:271](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L271) Get timestamp of last successful data update (in milliseconds) @@ -88,7 +88,7 @@ Get timestamp of last successful data update (in milliseconds) errorCount: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:198](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L198) +Defined in: [packages/query-db-collection/src/query.ts:263](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L263) Get the number of consecutive sync failures. Incremented only when query fails completely (not per retry attempt); reset on success. @@ -101,7 +101,7 @@ Incremented only when query fails completely (not per retry attempt); reset on s fetchStatus: "idle" | "fetching" | "paused"; ``` -Defined in: [packages/query-db-collection/src/query.ts:208](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L208) +Defined in: [packages/query-db-collection/src/query.ts:273](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L273) Get current fetch status @@ -113,7 +113,7 @@ Get current fetch status isError: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:193](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L193) +Defined in: [packages/query-db-collection/src/query.ts:258](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L258) Check if the collection is in an error state @@ -125,7 +125,7 @@ Check if the collection is in an error state isFetching: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:200](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L200) +Defined in: [packages/query-db-collection/src/query.ts:265](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L265) Check if query is currently fetching (initial or background) @@ -137,7 +137,7 @@ Check if query is currently fetching (initial or background) isLoading: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:204](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L204) +Defined in: [packages/query-db-collection/src/query.ts:269](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L269) Check if query is loading for the first time (no data yet) @@ -149,7 +149,7 @@ Check if query is loading for the first time (no data yet) isRefetching: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:202](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L202) +Defined in: [packages/query-db-collection/src/query.ts:267](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L267) Check if query is refetching in background (not initial fetch) @@ -161,7 +161,7 @@ Check if query is refetching in background (not initial fetch) lastError: TError | undefined; ``` -Defined in: [packages/query-db-collection/src/query.ts:191](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L191) +Defined in: [packages/query-db-collection/src/query.ts:256](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L256) Get the last error encountered by the query (if any); reset on success @@ -173,7 +173,7 @@ Get the last error encountered by the query (if any); reset on success refetch: RefetchFn; ``` -Defined in: [packages/query-db-collection/src/query.ts:177](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L177) +Defined in: [packages/query-db-collection/src/query.ts:242](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L242) Manually trigger a refetch of the query @@ -185,7 +185,7 @@ Manually trigger a refetch of the query writeBatch: (callback) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:187](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L187) +Defined in: [packages/query-db-collection/src/query.ts:252](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L252) Execute multiple write operations as a single atomic batch to the synced data store @@ -207,7 +207,7 @@ Execute multiple write operations as a single atomic batch to the synced data st writeDelete: (keys) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:183](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L183) +Defined in: [packages/query-db-collection/src/query.ts:248](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L248) Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update @@ -229,7 +229,7 @@ Delete one or more items directly from the synced data store without triggering writeInsert: (data) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:179](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L179) +Defined in: [packages/query-db-collection/src/query.ts:244](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L244) Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update @@ -251,7 +251,7 @@ Insert one or more items directly into the synced data store without triggering writeUpdate: (updates) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:181](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L181) +Defined in: [packages/query-db-collection/src/query.ts:246](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L246) Update one or more items directly in the synced data store without triggering a query refetch or optimistic update @@ -273,7 +273,7 @@ Update one or more items directly in the synced data store without triggering a writeUpsert: (data) => void; ``` -Defined in: [packages/query-db-collection/src/query.ts:185](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L185) +Defined in: [packages/query-db-collection/src/query.ts:250](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L250) Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update diff --git a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md index 68828fad88..5ceb7cea55 100644 --- a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md +++ b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md @@ -9,7 +9,7 @@ title: ApplyJoinOptionalityToMergedSchema type ApplyJoinOptionalityToMergedSchema = { [K in keyof TExistingSchema]: K extends TFromSourceNames ? TJoinType extends "right" | "full" ? TExistingSchema[K] | undefined : TExistingSchema[K] : TExistingSchema[K] } & { [K in keyof TNewSchema]: TJoinType extends "left" | "full" ? TNewSchema[K] | undefined : TNewSchema[K] }; ``` -Defined in: [packages/db/src/query/builder/types.ts:984](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L984) +Defined in: [packages/db/src/query/builder/types.ts:987](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L987) ApplyJoinOptionalityToMergedSchema - Applies optionality rules when merging schemas diff --git a/docs/reference/type-aliases/GetResult.md b/docs/reference/type-aliases/GetResult.md index 01f098bcb1..3b10139c7c 100644 --- a/docs/reference/type-aliases/GetResult.md +++ b/docs/reference/type-aliases/GetResult.md @@ -9,7 +9,7 @@ title: GetResult type GetResult = Prettify>; ``` -Defined in: [packages/db/src/query/builder/types.ts:1098](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1098) +Defined in: [packages/db/src/query/builder/types.ts:1101](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1101) ## Type Parameters diff --git a/docs/reference/type-aliases/InferResultType.md b/docs/reference/type-aliases/InferResultType.md index bd6de7a3d9..6a33c3baba 100644 --- a/docs/reference/type-aliases/InferResultType.md +++ b/docs/reference/type-aliases/InferResultType.md @@ -9,7 +9,7 @@ title: InferResultType type InferResultType = TContext extends SingleResult ? GetResult | undefined : GetResult[]; ``` -Defined in: [packages/db/src/query/builder/types.ts:1010](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1010) +Defined in: [packages/db/src/query/builder/types.ts:1013](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1013) Utility type to infer the query result size (single row or an array) diff --git a/docs/reference/type-aliases/MergeContextForJoinCallback.md b/docs/reference/type-aliases/MergeContextForJoinCallback.md index 6ac3820075..1873f80f38 100644 --- a/docs/reference/type-aliases/MergeContextForJoinCallback.md +++ b/docs/reference/type-aliases/MergeContextForJoinCallback.md @@ -9,7 +9,7 @@ title: MergeContextForJoinCallback type MergeContextForJoinCallback = object & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; ``` -Defined in: [packages/db/src/query/builder/types.ts:1255](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1255) +Defined in: [packages/db/src/query/builder/types.ts:1258](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1258) MergeContextForJoinCallback - Special context for join condition callbacks diff --git a/docs/reference/type-aliases/MergeContextWithJoinType.md b/docs/reference/type-aliases/MergeContextWithJoinType.md index e5655ebf6d..43c439caca 100644 --- a/docs/reference/type-aliases/MergeContextWithJoinType.md +++ b/docs/reference/type-aliases/MergeContextWithJoinType.md @@ -9,7 +9,7 @@ title: MergeContextWithJoinType type MergeContextWithJoinType = object & PreserveSingleResultFlag & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; ``` -Defined in: [packages/db/src/query/builder/types.ts:926](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L926) +Defined in: [packages/db/src/query/builder/types.ts:929](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L929) MergeContextWithJoinType - Creates a new context after a join operation diff --git a/docs/reference/type-aliases/Prettify.md b/docs/reference/type-aliases/Prettify.md index 89092056f7..ac4189e446 100644 --- a/docs/reference/type-aliases/Prettify.md +++ b/docs/reference/type-aliases/Prettify.md @@ -9,7 +9,7 @@ title: Prettify type Prettify = { [K in keyof T]: T[K] } & object; ``` -Defined in: [packages/db/src/query/builder/types.ts:1297](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1297) +Defined in: [packages/db/src/query/builder/types.ts:1300](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1300) Prettify - Utility type for clean IDE display diff --git a/docs/reference/type-aliases/Ref.md b/docs/reference/type-aliases/Ref.md index 5543f58749..79aca3b7ee 100644 --- a/docs/reference/type-aliases/Ref.md +++ b/docs/reference/type-aliases/Ref.md @@ -9,7 +9,7 @@ title: Ref type Ref = T extends unknown ? RefBranch : never; ``` -Defined in: [packages/db/src/query/builder/types.ts:824](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L824) +Defined in: [packages/db/src/query/builder/types.ts:827](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L827) Ref - The user-facing ref interface for the query builder diff --git a/docs/reference/type-aliases/RefsForContext.md b/docs/reference/type-aliases/RefsForContext.md index 9e011cb420..2634c6774b 100644 --- a/docs/reference/type-aliases/RefsForContext.md +++ b/docs/reference/type-aliases/RefsForContext.md @@ -9,7 +9,7 @@ title: RefsForContext type RefsForContext = { [K in KeysOfUnion>]: IsNonExactOptional, K>> extends true ? IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>>, true> : IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>> } & TContext["hasResult"] extends true ? object : object; ``` -Defined in: [packages/db/src/query/builder/types.ts:674](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L674) +Defined in: [packages/db/src/query/builder/types.ts:677](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L677) ## Type Parameters diff --git a/docs/reference/type-aliases/SchemaFromSource.md b/docs/reference/type-aliases/SchemaFromSource.md index 62abaf632d..c065f6c540 100644 --- a/docs/reference/type-aliases/SchemaFromSource.md +++ b/docs/reference/type-aliases/SchemaFromSource.md @@ -6,7 +6,7 @@ title: SchemaFromSource # Type Alias: SchemaFromSource\ ```ts -type SchemaFromSource = Prettify<{ [K in keyof T]: T[K] extends CollectionImpl ? InferCollectionType : T[K] extends QueryBuilder ? GetResult : never }>; +type SchemaFromSource = Prettify<{ [K in keyof T]: T[K] extends CollectionImpl ? InferCollectionType : T[K] extends QueryBuilder ? GetRawResult : never }>; ``` Defined in: [packages/db/src/query/builder/types.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L116) diff --git a/docs/reference/type-aliases/WithResult.md b/docs/reference/type-aliases/WithResult.md index 65160460f7..680631622b 100644 --- a/docs/reference/type-aliases/WithResult.md +++ b/docs/reference/type-aliases/WithResult.md @@ -9,7 +9,7 @@ title: WithResult type WithResult = Prettify & object>; ``` -Defined in: [packages/db/src/query/builder/types.ts:1287](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1287) +Defined in: [packages/db/src/query/builder/types.ts:1290](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1290) WithResult - Updates a context with a new result type after select() diff --git a/docs/reference/type-aliases/WithVirtualProps.md b/docs/reference/type-aliases/WithVirtualProps.md index 7a8b21aeaf..d5cec665fe 100644 --- a/docs/reference/type-aliases/WithVirtualProps.md +++ b/docs/reference/type-aliases/WithVirtualProps.md @@ -9,7 +9,7 @@ title: WithVirtualProps type WithVirtualProps = T & VirtualRowProps; ``` -Defined in: [packages/db/src/virtual-props.ts:112](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L112) +Defined in: [packages/db/src/virtual-props.ts:117](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L117) Adds virtual properties to a row type. diff --git a/docs/reference/type-aliases/WithoutVirtualProps.md b/docs/reference/type-aliases/WithoutVirtualProps.md index f7c3d864e2..e314776ce2 100644 --- a/docs/reference/type-aliases/WithoutVirtualProps.md +++ b/docs/reference/type-aliases/WithoutVirtualProps.md @@ -9,7 +9,7 @@ title: WithoutVirtualProps type WithoutVirtualProps = Omit; ``` -Defined in: [packages/db/src/virtual-props.ts:130](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L130) +Defined in: [packages/db/src/virtual-props.ts:135](https://github.com/TanStack/db/blob/main/packages/db/src/virtual-props.ts#L135) Extracts the base type from a type that may have virtual properties. Useful when you need to work with the raw data without virtual properties. From a9e13d55610aa2766fcc4cd79543953d1cd890d8 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 20 Jul 2026 08:25:30 -0600 Subject: [PATCH 56/58] test: cover late query readiness rejection (#1681) --- .../query-db-collection/tests/query.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index d72a7a3dac..ecf9178c05 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -2466,6 +2466,38 @@ describe(`QueryCollection`, () => { await collection.cleanup() }) + it(`keeps an unloaded preload resolved when its pending request later rejects`, async () => { + const deferred = createDeferred>() + const collection = createCollection( + queryCollectionOptions({ + id: `late-subset-rejection-test`, + queryClient, + queryKey: [`late-subset-rejection-test`], + queryFn: () => deferred.promise, + getKey, + syncMode: `on-demand`, + }), + ) + const liveQuery = createSubset(collection) + const preloadPromise = liveQuery.preload() + + await vi.waitFor(() => expect(queryClient.isFetching()).toBe(1)) + await liveQuery.cleanup() + + const subsetQuery = queryClient.getQueryCache().findAll({ + queryKey: [`late-subset-rejection-test`], + })[0] + expect(subsetQuery?.getObserversCount() ?? 0).toBe(0) + await expect(preloadPromise).resolves.toBeUndefined() + + deferred.reject(new Error(`Late query failure`)) + await vi.waitFor(() => expect(queryClient.isFetching()).toBe(0)) + + expect(collection.size).toBe(0) + expect(subsetQuery?.getObserversCount() ?? 0).toBe(0) + await collection.cleanup() + }) + it(`preserves an active subset across cache removal and accepts a late notification from its detached observer`, async () => { const queryKey = [`cache-removal-late-notification-test`] const collection = createCollection( From 932910d97caad3dd490274d02189cbf1f8c53442 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 20 Jul 2026 08:35:17 -0600 Subject: [PATCH 57/58] fix(query-db): clean up empty ownership sets (#1672) * test(query-db): characterize ownership cleanup lifecycle * test(query-db): cover ownership hydration and cache expiry * fix(query-db): remove empty ownership entries * fix(query-db): bound ownership resolution tracking * fix(query-db): clear retained ownership marker * refactor(query-db): simplify ownership cleanup * chore: add query ownership cleanup changeset * chore: remove workflow artifact * refactor: simplify query ownership bookkeeping * fix(query-db): clean retained ownership on cleanup --- .changeset/clean-query-ownership.md | 5 + packages/query-db-collection/src/query.ts | 46 ++- .../query-db-collection/tests/query.test.ts | 266 ++++++++++++++++++ 3 files changed, 307 insertions(+), 10 deletions(-) create mode 100644 .changeset/clean-query-ownership.md diff --git a/.changeset/clean-query-ownership.md b/.changeset/clean-query-ownership.md new file mode 100644 index 0000000000..b86854f275 --- /dev/null +++ b/.changeset/clean-query-ownership.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Clean up empty query ownership state while preserving authoritative empty results and retained-row lifecycle behavior. diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index f104cba3b1..6773b22414 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -737,7 +737,8 @@ export function queryCollectionOptions( // hashedQueryKey → queryKey const hashToQueryKey = new Map() - // queryKey → Set + // queryKey → Set. Entry presence means ownership is resolved; + // an empty set represents a resolved query that currently owns no rows. const queryToRows = new Map>() // RowKey → Set @@ -776,6 +777,11 @@ export function queryCollectionOptions( } const addRowOwners = (rowKey: string | number, owners: Set) => { + if (owners.size === 0) { + rowToQueries.delete(rowKey) + return + } + rowToQueries.set(rowKey, new Set(owners)) owners.forEach((owner) => { const ownedRows = queryToRows.get(owner) || new Set() @@ -785,16 +791,16 @@ export function queryCollectionOptions( } const removeRowOwner = (rowKey: string | number, hashedQueryKey: string) => { - const owners = rowToQueries.get(rowKey) || new Set() - owners.delete(hashedQueryKey) - rowToQueries.set(rowKey, owners) + const owners = rowToQueries.get(rowKey) + owners?.delete(hashedQueryKey) + if (!owners?.size) { + rowToQueries.delete(rowKey) + } - const ownedRows = - queryToRows.get(hashedQueryKey) || new Set() - ownedRows.delete(rowKey) - queryToRows.set(hashedQueryKey, ownedRows) + const ownedRows = queryToRows.get(hashedQueryKey) + ownedRows?.delete(rowKey) - return owners.size === 0 + return !owners?.size } const removeQueryOwnership = (hashedQueryKey: string) => { @@ -1076,6 +1082,7 @@ export function queryCollectionOptions( `${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`, ) commit() + queryToRows.delete(hashedQueryKey) } const schedulePersistedRetentionExpiry = ( @@ -1391,6 +1398,13 @@ export function queryCollectionOptions( const previouslyOwnedRows = shouldUsePersistedBaseline ? new Set(persistedBaseline.keys()) : getHydratedOwnedRowsForQueryBaseline(hashedQueryKey) + // From this point onward the result, including an empty result, is the + // authoritative ownership baseline until this query is cleaned up. + queryToRows.set( + hashedQueryKey, + queryToRows.get(hashedQueryKey) ?? new Set(), + ) + const newItemsMap = new Map() newItemsArray.forEach((item) => { const key = getKey(item) @@ -1760,7 +1774,10 @@ export function queryCollectionOptions( persistedRetentionTimers.clear() const allQueryKeys = [...hashToQueryKey.values()] - const allHashedKeys = [...state.observers.keys()] + const allHashedKeys = new Set([ + ...state.observers.keys(), + ...queryToRows.keys(), + ]) // Force cleanup all queries (explicit cleanup path) // This ignores hasListeners and always cleans up @@ -2028,6 +2045,15 @@ export function queryCollectionOptions( } } + if (typeof process !== `undefined` && process.env.NODE_ENV === `test`) { + Object.defineProperty(enhancedInternalSync, `__getOwnershipMapsForTests`, { + value: () => ({ + rowToQueries, + queryToRows, + }), + }) + } + // Create write utils using the manual-sync module const writeUtils = createWriteUtils( () => writeContext, diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index ecf9178c05..82b791818c 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -47,6 +47,28 @@ interface CategorisedItem { const getKey = (item: TestItem) => item.id +type OwnershipMaps = { + rowToQueries: Map> + queryToRows: Map> +} + +function inspectOwnershipMaps(options: { + sync: { sync: unknown } +}): OwnershipMaps { + const sync = options.sync.sync as { + __getOwnershipMapsForTests?: () => OwnershipMaps + } + const maps = sync.__getOwnershipMapsForTests?.() + if (!maps) { + throw new Error(`Ownership-map test inspection is unavailable`) + } + return maps +} + +function expectNoEmptyRowOwnershipSets(maps: OwnershipMaps): void { + maps.rowToQueries.forEach((owners) => expect(owners.size).toBeGreaterThan(0)) +} + // Helper to advance timers and allow microtasks to flush const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0)) @@ -5329,6 +5351,193 @@ describe(`QueryCollection`, () => { }) }) + describe(`ownership lifecycle characterization`, () => { + it(`removes only rows whose final subset owner is unloaded`, async () => { + const queryFn = vi + .fn() + .mockResolvedValueOnce([ + { id: `1`, name: `First only` }, + { id: `2`, name: `Shared` }, + ]) + .mockResolvedValueOnce([ + { id: `2`, name: `Shared` }, + { id: `3`, name: `Second only` }, + ]) + const options = queryCollectionOptions({ + id: `ownership-overlapping-subsets-test`, + queryClient, + queryKey: [`ownership-overlapping-subsets-test`], + queryFn, + getKey, + syncMode: `on-demand`, + }) + const ownershipMaps = inspectOwnershipMaps(options) + const collection = createCollection(options) + const firstSubset = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => inArray(item.id, [`1`, `2`])), + }) + const secondSubset = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => inArray(item.id, [`2`, `3`])), + }) + + await firstSubset.preload() + await secondSubset.preload() + expect(collection.size).toBe(3) + + await firstSubset.cleanup() + await vi.waitFor(() => { + expect(collection.has(`1`)).toBe(false) + expect(collection.has(`2`)).toBe(true) + expect(collection.has(`3`)).toBe(true) + }) + expectNoEmptyRowOwnershipSets(ownershipMaps) + + await secondSubset.cleanup() + await vi.waitFor(() => { + expect(collection.size).toBe(0) + }) + expectNoEmptyRowOwnershipSets(ownershipMaps) + expect(ownershipMaps.rowToQueries.size).toBe(0) + expect(ownershipMaps.queryToRows.size).toBe(0) + }) + + it(`expires the Query cache entry after unload without restoring deleted rows`, async () => { + const expiringQueryClient = new QueryClient({ + defaultOptions: { + queries: { gcTime: 100, retry: false, staleTime: Infinity }, + }, + }) + const queryKey = [`ownership-query-cache-expiry-test`] + const collection = createCollection( + queryCollectionOptions({ + id: `ownership-query-cache-expiry-test`, + queryClient: expiringQueryClient, + queryKey, + queryFn: async () => [{ id: `1`, name: `Only row` }], + getKey, + syncMode: `on-demand`, + }), + ) + const liveQuery = createLiveQueryCollection({ + query: (q) => q.from({ item: collection }), + }) + + await liveQuery.preload() + expect(collection.has(`1`)).toBe(true) + + vi.useFakeTimers() + try { + await liveQuery.cleanup() + expect(collection.size).toBe(0) + expect( + expiringQueryClient.getQueryCache().find({ queryKey }), + ).toBeDefined() + + await vi.advanceTimersByTimeAsync(101) + + expect( + expiringQueryClient.getQueryCache().find({ queryKey }), + ).toBeUndefined() + expect(collection.size).toBe(0) + } finally { + vi.useRealTimers() + expiringQueryClient.clear() + } + }) + + it(`hydrates a retained row and deletes it when its query revalidates empty`, async () => { + const queryKey = [`ownership-retained-hydration-test`] + const queryHash = hashKey(queryKey) + const retainedRow: CategorisedItem = { + id: `1`, + name: `Retained row`, + category: `A`, + } + let releaseRevalidation!: () => void + const revalidationReleased = new Promise((resolve) => { + releaseRevalidation = resolve + }) + const queryFn = vi.fn(async () => { + await revalidationReleased + return [] + }) + const baseOptions = queryCollectionOptions({ + id: `ownership-retained-hydration-test`, + queryClient, + queryKey, + queryFn, + getKey: (item) => item.id, + startSync: true, + }) + const originalSync = baseOptions.sync + const ownershipMaps = inspectOwnershipMaps(baseOptions) + const metadataHarness = createInMemorySyncMetadataApi< + string | number, + CategorisedItem + >({ + persistedRows: new Map([[retainedRow.id, retainedRow]]), + rowMetadata: new Map([ + [ + retainedRow.id, + { + queryCollection: { owners: { [queryHash]: true } }, + }, + ], + ]), + collectionMetadata: new Map([ + [ + `queryCollection:gc:${queryHash}`, + { queryHash, mode: `until-revalidated` }, + ], + ]), + }) + const collection = createCollection({ + ...baseOptions, + sync: { + sync: (params: Parameters[0]) => { + params.begin({ immediate: true }) + params.write({ type: `insert`, value: retainedRow }) + params.commit() + return originalSync.sync({ + ...params, + metadata: metadataHarness.api, + }) + }, + }, + }) + + expect(collection.has(retainedRow.id)).toBe(true) + expect( + metadataHarness.collectionMetadata.has( + `queryCollection:gc:${queryHash}`, + ), + ).toBe(true) + + releaseRevalidation() + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.has(retainedRow.id)).toBe(false) + }) + expect(metadataHarness.rowMetadata.get(retainedRow.id)).toBeUndefined() + expectNoEmptyRowOwnershipSets(ownershipMaps) + expect(ownershipMaps.rowToQueries.size).toBe(0) + expect(ownershipMaps.queryToRows.get(queryHash)).toEqual(new Set()) + expect( + metadataHarness.collectionMetadata.has( + `queryCollection:gc:${queryHash}`, + ), + ).toBe(false) + + await collection.cleanup() + }) + }) + it(`should handle duplicate subset loads correctly (refcount bug)`, async () => { // This test catches Bug 1: missing refcount increment when reusing existing observer // When two subscriptions load the same subset, unloading one should NOT destroy @@ -5926,6 +6135,7 @@ describe(`QueryCollection`, () => { const baseOptions = queryCollectionOptions(config) const originalSync = baseOptions.sync + const ownershipMaps = inspectOwnershipMaps(baseOptions) const metadataHarness = createInMemorySyncMetadataApi< string | number, CategorisedItem @@ -5958,6 +6168,13 @@ describe(`QueryCollection`, () => { await liveQuery.cleanup() + expect(ownershipMaps.queryToRows.has(retainedQueryHash)).toBe(true) + expect(ownershipMaps.queryToRows.get(retainedQueryHash)).toEqual( + new Set([`1`]), + ) + expect(ownershipMaps.rowToQueries.get(`1`)).toEqual( + new Set([retainedQueryHash]), + ) expect( metadataHarness.collectionMetadata.get( `queryCollection:gc:${retainedQueryHash}`, @@ -5977,11 +6194,60 @@ describe(`QueryCollection`, () => { ), ).toBeUndefined() expect(collection.has(`1`)).toBe(false) + expect(ownershipMaps.queryToRows.has(retainedQueryHash)).toBe(false) + expect(ownershipMaps.rowToQueries.has(`1`)).toBe(false) } finally { vi.useRealTimers() } }) + it(`should clear retained ownership during explicit collection cleanup`, async () => { + const baseQueryKey = [`explicit-retained-cleanup-test`] + const retainedQueryHash = hashKey(baseQueryKey) + const items: Array = [ + { id: `1`, name: `Retained`, category: `A` }, + ] + const config: QueryCollectionConfig = { + id: `explicit-retained-cleanup-test`, + queryClient, + queryKey: () => baseQueryKey, + queryFn: async () => items, + getKey: (item) => item.id, + syncMode: `on-demand`, + startSync: true, + persistedGcTime: 60_000, + } + const baseOptions = queryCollectionOptions(config) + const originalSync = baseOptions.sync + const ownershipMaps = inspectOwnershipMaps(baseOptions) + const metadataHarness = createInMemorySyncMetadataApi< + string | number, + CategorisedItem + >({ persistedRows: new Map(items.map((item) => [item.id, item])) }) + const collection = createCollection({ + ...baseOptions, + sync: { + sync: (params: Parameters[0]) => + originalSync.sync({ ...params, metadata: metadataHarness.api }), + }, + }) + const liveQuery = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => eq(item.category, `A`)), + }) + + await liveQuery.preload() + await liveQuery.cleanup() + expect(ownershipMaps.queryToRows.has(retainedQueryHash)).toBe(true) + + await collection.cleanup() + + expect(ownershipMaps.queryToRows.size).toBe(0) + expect(ownershipMaps.rowToQueries.size).toBe(0) + }) + it(`should default persisted retention ttl to query gcTime when persistedGcTime is undefined`, async () => { vi.useFakeTimers() const gcTime = 120 From f42db5cacbb559b8cf31e6cc376c13044a45ff58 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 20 Jul 2026 16:08:01 -0600 Subject: [PATCH 58/58] feat(query-db): support eager initial data (#1683) * docs(query-db): design initial data semantics * feat(query-db): support eager initial data * Apply code simplification and review fixes * fix(query-db): revalidate persisted initial data * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/young-cats-initialize.md | 7 + docs/collections/query-collection.md | 58 ++- .../query-initial-placeholder-data-design.md | 249 +++++++++ packages/query-db-collection/src/errors.ts | 9 + packages/query-db-collection/src/query.ts | 71 +++ .../query-db-collection/tests/query.test.ts | 481 ++++++++++++++++++ 6 files changed, 874 insertions(+), 1 deletion(-) create mode 100644 .changeset/young-cats-initialize.md create mode 100644 docs/collections/query-initial-placeholder-data-design.md diff --git a/.changeset/young-cats-initialize.md b/.changeset/young-cats-initialize.md new file mode 100644 index 0000000000..483c4fb569 --- /dev/null +++ b/.changeset/young-cats-initialize.md @@ -0,0 +1,7 @@ +--- +'@tanstack/query-db-collection': minor +--- + +Add eager collection support for TanStack Query `initialData` and `initialDataUpdatedAt`, including wrapped response projection and collection-local initialization on shared QueryClient instances. + +QueryClient-default `placeholderData` no longer materializes as collection rows, and QueryClient-default `initialData` no longer seeds on-demand subset observers. diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index c2635453be..e55fb94138 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -200,6 +200,8 @@ The following top-level Query Collection options are forwarded to the underlying - `refetchOnReconnect`: Whether to refetch when the network reconnects - `refetchOnMount`: Whether to refetch when the observer mounts - `networkMode`: Query network mode +- `initialData`: Initial Query response for eager collections +- `initialDataUpdatedAt`: Timestamp used by TanStack Query to determine initial data freshness - `meta`: Metadata passed to the query function context. Query Collections may add `loadSubsetOptions` for on-demand queries. ```ts @@ -237,7 +239,7 @@ Some TanStack Query fields are owned or reinterpreted by Query Collection and ar - `subscribed` (Query Collection owns the observer subscription lifecycle) - `structuralSharing` and `notifyOnChangeProps` (managed by Query Collection synchronization) -Other semantic options, such as `initialData`, `placeholderData`, and TanStack Query observer-level `select`, are intentionally deferred until their Query Collection behavior is explicitly designed. +`placeholderData` is intentionally unsupported. TanStack Query treats placeholder data as observer-local presentation state rather than cached Query data. Materializing it would expose temporary UI data as collection-wide normalized rows. Render placeholders in the consuming UI instead. ### Using with `queryOptions(...)` @@ -271,6 +273,51 @@ const todosCollection = createCollection( If `queryFn` is missing at runtime, `queryCollectionOptions` throws `QueryFnRequiredError`. +### Initial Data + +Eager Query Collections support TanStack Query's `initialData` and +`initialDataUpdatedAt` options. Initial data has the original Query response +shape, is stored in the Query cache, and is immediately materialized as +normalized collection rows. TanStack Query uses `initialDataUpdatedAt` together +with `staleTime` to decide whether to fetch. + +```typescript +const serverRenderedAt = Date.now() +const initialTodos = [ + { id: "1", title: "Write documentation" }, + { id: "2", title: "Ship initial data support" }, +] + +const todosCollection = createCollection( + queryCollectionOptions({ + queryKey: ["todos"], + queryFn: fetchTodos, + queryClient, + getKey: (todo) => todo.id, + initialData: initialTodos, + initialDataUpdatedAt: serverRenderedAt, + staleTime: 60_000, + }), +) +``` + +An existing cached or hydrated Query response takes precedence over +`initialData`. Query keys remain the cache identity: two collections using the +same QueryClient and exact Query key observe one shared Query document, and a +later collection's initializer does not replace it. Use distinct Query keys for +independent documents. + +Initial data is supported only for eager collections. A collection-wide value +cannot establish row membership for arbitrary on-demand predicates, ordering, +limits, and offsets. For `syncMode: "on-demand"`, seed or hydrate the exact +derived Query cache entries instead. + +If a stale initial response triggers a fetch, the initial rows remain available +while it is in flight. A successful response reconciles them through the normal +row ownership pipeline; an error retains the initial rows. Direct writes use the +same Query cache-patching rules as fetched data, and a later successful server +response may reconcile or replace those writes. + ### Selecting Rows from Wrapped Responses Many APIs return rows inside a response envelope that also contains metadata such as pagination cursors, totals, or request information. Use `select` to extract the row array that TanStack DB should materialize: @@ -289,6 +336,11 @@ const todosCollection = createCollection( const response = await fetch("/api/todos") return response.json() }, + initialData: { + items: [{ id: "1", title: "Initial todo" }], + nextCursor: undefined, + total: 1, + }, select: (response) => response.items, queryClient, getKey: (item) => item.id, @@ -298,6 +350,10 @@ const todosCollection = createCollection( `select` is a query-db-collection row extraction hook. It tells TanStack DB which rows to materialize while the TanStack Query cache keeps the original query response shape. In the example above, `queryClient.getQueryData(["todos"])` still returns the full `TodosResponse`, including `nextCursor` and `total`. +The same projection applies to `initialData`: provide the complete response +envelope, and Query Collection materializes the rows returned by `select` while +preserving the envelope in the Query cache. + This differs from TanStack Query's observer-level `select`: query-db-collection uses this option to bridge Query's response object into DB's normalized row store. Direct write utilities such as `writeInsert`, `writeUpdate`, and `writeDelete` make a best-effort attempt to update the matching row array inside wrapped Query cache entries while preserving wrapper metadata. diff --git a/docs/collections/query-initial-placeholder-data-design.md b/docs/collections/query-initial-placeholder-data-design.md new file mode 100644 index 0000000000..9ee866f325 --- /dev/null +++ b/docs/collections/query-initial-placeholder-data-design.md @@ -0,0 +1,249 @@ +# Query Collection initial and placeholder data semantics + +## Status and scope + +This document defines the semantics for TanStack Query `initialData` and +`placeholderData` at the `@tanstack/query-db-collection` boundary. It is the +design follow-up for [RFC #1643](https://github.com/TanStack/db/issues/1643) +and [issue #346](https://github.com/TanStack/db/issues/346). The accompanying +implementation adds the approved eager `initialData` behavior without changing +the persistence format. + +The adapter connects two different models: + +- TanStack Query owns a document cache and remote-query lifecycle. +- TanStack DB owns normalized rows, local queries, and optimistic writes. +- Query Collection projects a Query response into rows and records which Query + key owns each materialized row. + +`initialData` and `placeholderData` must not be treated as equivalent ways to +provide an array. In Query Core 5.90.20, `initialData` initializes Query cache +state with `status: "success"` and a `dataUpdatedAt` timestamp. By contrast, +`placeholderData` is computed per observer only while Query state is pending; +it produces an observer result with `isPlaceholderData: true` but is not stored +in Query state. + +## Decision + +Support Query-owned `initialData` as an additive, eager-mode option. Materialize +it immediately through the existing row extraction and ownership pipeline. +Do not expose or materialize `placeholderData` in the first implementation. + +Query Collection can already materialize data that was seeded or hydrated into +the QueryClient before the observer is created, and QueryClient defaults can +already provide `initialData` indirectly. That is useful existing behavior, but +it does not close the configuration gap. Applications commonly share one +QueryClient across many Query Collections: a client-wide default is too broad, +while imperative `setQueryData` requires coordinating collection construction, +exact Query keys, and initialization elsewhere. The additive field supplies a +collection-local declaration while leaving Query as the cache authority. + +The minimal additive API is: + +```ts +initialData?: TQueryData | (() => TQueryData) +initialDataUpdatedAt?: number | (() => number | undefined) +``` + +These remain flat top-level fields. Their types describe the original Query +response, not the extracted row array. Consequently, wrapped responses use the +same adapter `select` as network responses: + +```ts +queryCollectionOptions({ + queryKey: ["todos"], + queryFn: fetchTodos, + initialData: { items: serverTodos, nextCursor: null }, + select: (response) => response.items, + // ... +}) +``` + +Query keys still define cache identity. If two Query Collections on the same +QueryClient use the same exact key, they observe one shared Query document; +`initialData` initializes that document only when it does not already exist. +Collection-local configuration does not create collection-local cache data, and +later observers must not replace an existing document with their initializer. +Collections that require independent initial documents must use distinct keys. + +This initial API is limited to `syncMode: "eager"`. A single configuration-level +value cannot lawfully initialize an open-ended family of on-demand subset keys, +and Query's `initialData` function receives no query key or subset context. +Applications that already know data for an exact on-demand key should seed or +hydrate that Query cache entry instead. A future subset-aware initializer would +need an explicit key/subset argument and a separate design. + +`placeholderData` remains Query UI vocabulary. A DB collection has no +observer-local result surface: materializing a placeholder would make it visible +to every DB query and mutation, assign it row ownership, and potentially persist +it. Callers should render placeholders in the consuming UI. A future opt-in +temporary-row feature, if needed, should be a DB feature with explicit provenance +and lifecycle rather than Query's `placeholderData` option. + +## Authority model + +"Authoritative" has two dimensions here. Query owns the authoritative document +for a Query key, while DB owns the current normalized row state. A server result +is the newest remote snapshot, but local optimistic transactions can temporarily +overlay its rows. + +| Phase | Query document authority | Materialized row authority | Consequence | +| --- | --- | --- | --- | +| No cached data | None | Existing DB/persisted rows, if any | The collection waits for Query; absence is not an empty result. | +| Initial data | Query cache `initialData` | Its projected rows, subject to normal local overlays | It is a real cached snapshot, not temporary presentation data. | +| Fetch/refetch in flight | Existing Query document | Existing rows | Loading does not clear rows or ownership. | +| Server success | Returned response replaces the Query document | Projected server rows reconcile the owning Query key | Missing rows lose this Query owner's lease; shared rows remain. | +| Fetch/refetch error | Last successful Query document | Existing rows | An error does not retract initial or previously fetched rows. | +| Placeholder presentation | No Query document | No rows | Placeholder data is never passed to DB. | + +`initialDataUpdatedAt` and `staleTime` remain Query-owned. They decide whether a +fetch starts; the adapter does not reproduce their freshness calculation. An +initial value is therefore a seed snapshot with ordinary Query authority, not a +weaker class of row waiting to be promoted. The first successful server result +reconciles it through the same path as any later refetch. + +## Behavior matrix + +| Concern | `initialData` | `placeholderData` | +| --- | --- | --- | +| Query cache | Stored as successful Query data | Not stored; observer-only | +| Existing cache entry | Existing cached/hydrated data wins; `initialData` is not reapplied | Not applicable | +| DB materialization | Immediate in eager mode | Never | +| Wrapped response | Adapter `select(initialData)` extracts rows; original envelope stays cached | Unsupported | +| Function value | Evaluated by Query once when the Query is created | Not forwarded or evaluated by the adapter | +| Ownership | The Query key owns projected rows exactly like a server success | No ownership | +| Overlapping subsets | Not applicable to the initial eager-only API | Not applicable | +| Ready state | Initial successful result can make the collection ready synchronously | Cannot make the collection ready | +| Refetch success | Reconciles additions, updates, and removals normally | N/A | +| Refetch error | Initial rows and ownership remain; error state is reported | N/A | +| Cancellation/cleanup | Existing ownership cleanup rules apply; a cancelled fetch does not retract the cached seed | No rows to clean up | +| Query cache GC/unload | Existing Query-to-row ownership and persisted-retention rules apply | No effect | +| Query dehydration | Query owns persistence of the initial response | Never persisted | +| DB persistence/hydration | Rows and owner metadata use the existing format; no provenance tag is added | Never persisted or hydrated | +| Direct writes before server success | Allowed under the existing write rules below | No target rows exist | +| QueryClient defaults | Supported for eager `initialData`; see compatibility guard below | Must be suppressed at this adapter boundary | + +## Select and writes + +Adapter `select` remains a one-way row extractor. It is applied identically to +initial and network responses, and TanStack Query retains the original response +shape. Query observer-level `select` remains unsupported. + +Direct writes before the first server result follow the current authority rule: +they update DB immediately and may patch Query cache only when the reverse update +is lawful. A raw array can be replaced. For a wrapped response, the existing +best-effort patch is lawful only when `select` returns an array property of the +cached object by reference, allowing the wrapper to be preserved. A derived +projection such as `response.edges.map(...)` has no general reverse projection; +the adapter must leave that Query document unchanged and rely on invalidate or +refetch. It must never fabricate an envelope around rows. + +The next successful remote response remains authoritative for that Query key and +may overwrite a direct cache patch or normalized row value. Mutation handlers and +optimistic transaction barriers retain their existing semantics. + +## Ownership, persistence, and transitions + +Initial rows use the existing `queryToRows` and `rowToQueries` relationship. No +`seed`, `temporary`, or `placeholder` bit is added to a row. This keeps these +invariants intact: + +1. A successful result, whether initial or fetched, is a complete snapshot for + its Query key. +2. A row is deleted when a snapshot omits it only if no other Query key owns it. +3. Unload, cache GC, and collection cleanup remove only the relevant ownership. +4. A failed or cancelled fetch cannot turn the last successful snapshot into an + empty snapshot. +5. Persistence records only Query data and the existing row ownership metadata; + observer-only presentation state is never persisted. + +The expected transitions are: + +- **Initial, fresh:** materialize and become ready; do not fetch until Query's + normal freshness triggers say to do so. +- **Initial, stale:** materialize and become ready while Query fetches; success + reconciles the same ownership, and error retains the seed. +- **Loading without data:** keep the collection's prior independently owned or + hydrated rows; do not infer an empty result. +- **Placeholder to loading/success/error:** the placeholder is UI-only. DB sees + no transition until success; error leaves DB unchanged. +- **Cleanup before network completion:** existing cancellation and readiness + listener cleanup apply. A late result must not mutate a cleaned-up collection. + +## Defaults and compatibility guard + +Query Collection currently constructs a `QueryObserver`, so QueryClient defaults +can contain semantic fields even when Query Collection does not expose them. +Implementation must explicitly enforce this design after Query defaults are +resolved: + +- reject an explicitly configured `initialData` in on-demand mode; +- prevent default `initialData` from initializing on-demand subset observers; +- prevent explicit or default `placeholderData` from reaching all Query + Collection observers; +- continue to let omitted eager `initialData` and `initialDataUpdatedAt` inherit + QueryClient defaults; +- never copy a function-valued initializer into adapter metadata or persisted + ownership metadata. Query Core may own it as an option and stores only its + evaluated data in Query state. + +Silently materializing default placeholder data would violate the public +compatibility table even before a top-level field is added. The guard is therefore +a correctness fix, not support for placeholder semantics. + +Other Query-owned options keep their current classification. Query key creation, +adapter `select`, subscriptions, `notifyOnChangeProps`, and structural sharing +remain adapter-owned or reinterpreted. No nested `queryOptions`, runtime binding +API, subset deduplication, or lease manager is introduced by this design. + +## Rejected designs + +- **Forward both options mechanically.** `result.isSuccess` is true for a + placeholder observer result, so the current success handler would normalize + presentation-only data and give it durable-looking ownership. +- **Tag placeholder rows and later promote or delete them.** Tags would have to + survive collisions with real rows, overlapping observers, local writes, + unload, GC, persistence, and hydration. Query's observer-local placeholder has + no collection-wide lifetime that can drive those transitions safely. +- **Treat initial rows as unowned temporary rows.** Query considers initial data + real cached data. Bypassing normal ownership would leak rows or allow cleanup + of one Query to remove rows still represented by another. +- **Seed every on-demand key from one value.** A collection-level initializer + cannot prove membership in arbitrary predicate/order/limit subsets. +- **Create a wrapped response from selected rows.** A read projection is not an + inverse. Fabricating metadata, cursors, or edges corrupts Query cache meaning. +- **Persist initializer functions.** Functions are not structured-clone safe and + are runtime configuration, not data. + +## Implementation and test sequence + +Each behavior PR should begin with the named failing or characterization tests. + +1. **Guard the existing boundary.** Add focused tests proving that explicit and + QueryClient-default `placeholderData` never materialize, never mark the + collection ready, and never survive dehydration as data. Characterize current + default `initialData` behavior in eager and on-demand modes. Then suppress + placeholder and on-demand initialization when constructing observers. +2. **Add eager initial data.** Add the two flat typed fields and forward only + defined values so QueryClient defaults remain intact. Test static and function + values, fresh versus stale timestamps, synchronous readiness, fetch error, + cancellation, cleanup, an explicit on-demand configuration error, multiple + collections on one QueryClient, and same-key first-initializer-wins behavior. +3. **Lock projection and writes.** Test raw arrays, direct-property wrapped + responses, and derived projections. Verify the full initial envelope remains + in Query cache, lawful direct writes preserve it, unlawful reverse projection + does not fabricate one, and server success reconciles rows. +4. **Lock ownership.** Test initial-to-server row removal, overlapping ownership + with externally hydrated/persisted rows, GC/unload, remount within `gcTime`, + and late notification after cleanup. Reuse the existing ownership machinery; + do not add a second seed ownership map. +5. **Lock persistence.** Dehydrate and `structuredClone` initial raw and wrapped + data; hydrate Query and DB state; verify ownership reconciliation and that no + function appears in Query metadata or adapter persistence metadata. +6. **Document the shipped API.** Move the settled behavior into the Query Options, + row extraction, direct writes, on-demand, and persistence sections of the user + guide. Update RFC #1643 and close or narrow #346 only after these contracts ship. + +Placeholder materialization, subset-aware initialization, and a lawful general +reverse projection are separate future proposals. None is a prerequisite for the +minimal eager `initialData` API. diff --git a/packages/query-db-collection/src/errors.ts b/packages/query-db-collection/src/errors.ts index 7224745255..2a226181bc 100644 --- a/packages/query-db-collection/src/errors.ts +++ b/packages/query-db-collection/src/errors.ts @@ -36,6 +36,15 @@ export class GetKeyRequiredError extends QueryCollectionError { } } +export class InitialDataInOnDemandModeError extends QueryCollectionError { + constructor() { + super( + `[QueryCollection] initialData and initialDataUpdatedAt are only supported when syncMode is 'eager'. Seed or hydrate the exact Query cache key for on-demand subsets instead.`, + ) + this.name = `InitialDataInOnDemandModeError` + } +} + export class SyncNotInitializedError extends QueryCollectionError { constructor() { super( diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 6773b22414..82f2a8dde5 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -2,6 +2,7 @@ import { QueryObserver, hashKey } from '@tanstack/query-core' import { deepEquals } from '@tanstack/db' import { GetKeyRequiredError, + InitialDataInOnDemandModeError, QueryClientRequiredError, QueryFnRequiredError, QueryKeyRequiredError, @@ -191,6 +192,26 @@ export interface QueryCollectionConfig< TQueryData, TQueryKey >[`networkMode`] + /** + * Data used to initialize the TanStack Query cache for an eager collection. + * The value has the original Query response shape and is projected through + * the collection's select option before rows are materialized. + */ + initialData?: QueryObserverOptions< + TQueryData, + TError, + Array, + TQueryData, + TQueryKey + >[`initialData`] + /** The timestamp TanStack Query uses to determine initialData freshness. */ + initialDataUpdatedAt?: QueryObserverOptions< + TQueryData, + TError, + Array, + TQueryData, + TQueryKey + >[`initialDataUpdatedAt`] persistedGcTime?: number /** @@ -662,6 +683,8 @@ export function queryCollectionOptions( refetchOnReconnect, refetchOnMount, networkMode, + initialData, + initialDataUpdatedAt, persistedGcTime, getKey, onInsert, @@ -674,6 +697,35 @@ export function queryCollectionOptions( // Default to eager sync mode if not provided const syncMode = baseCollectionConfig.syncMode ?? `eager` + if ( + syncMode === `on-demand` && + (initialData !== undefined || initialDataUpdatedAt !== undefined) + ) { + throw new InitialDataInOnDemandModeError() + } + + const initialDataObserverOptions = + syncMode === `eager` + ? { + ...(initialData !== undefined && { initialData }), + ...(initialDataUpdatedAt !== undefined && { + initialDataUpdatedAt, + }), + // Placeholder data is observer-local UI state in TanStack Query. It + // must not be exposed as collection-wide normalized rows, including + // when supplied through QueryClient defaults. + placeholderData: undefined, + } + : { + // A collection-wide initializer cannot establish membership for + // arbitrary on-demand subsets. A defined initializer is needed to + // override a QueryClient default because Query Core can apply + // defaults again while constructing each Query. + initialData: () => undefined, + initialDataUpdatedAt: undefined, + placeholderData: undefined, + } + // Compute the base query key once for cache lookups. // All derived keys (from on-demand predicates or function-based queryKey) must // share this prefix so that queryCache.findAll({ queryKey: baseKey }) can find them. @@ -1295,6 +1347,7 @@ export function queryCollectionOptions( refetchOnMount, networkMode, }), + ...initialDataObserverOptions, queryKey: key, queryFn: queryFunction, meta: extendedMeta, @@ -1493,6 +1546,24 @@ export function queryCollectionOptions( } if (retainedQueriesPendingRevalidation.has(hashedQueryKey)) { + const query = queryClient.getQueryCache().find({ + queryKey, + exact: true, + }) + if (query?.state.dataUpdateCount === 0) { + // Initial data seeds Query state without a fetch. It must not + // satisfy persistence retention that lasts until revalidation. + if (!result.isFetching) { + state.observers + .get(hashedQueryKey) + ?.refetch() + .catch(() => { + // Errors handled by the next handleQueryResult invocation + }) + } + return + } + void reconcileSuccessfulResult(queryKey, result).catch((error) => { console.error( `[QueryCollection] Error reconciling query ${String(queryKey)}:`, diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 82b791818c..a4e4350d05 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -254,6 +254,404 @@ describe(`QueryCollection`, () => { expect(options.networkMode).toBe(`online`) }) + describe(`initialData`, () => { + it(`materializes eager initial data without fetching while it is fresh`, async () => { + const queryKey = [`initial-data-eager`] + const initialData: Array = [{ id: `1`, name: `Initial item` }] + const queryFn = vi + .fn<() => Promise>>() + .mockResolvedValue([{ id: `1`, name: `Fetched item` }]) + + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-eager`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + initialData, + staleTime: Infinity, + }), + ) + + try { + await vi.waitFor(() => { + expect(collection.status).toBe(`ready`) + expect(stripVirtualProps(collection.get(`1`))).toEqual(initialData[0]) + }) + + expect(queryFn).not.toHaveBeenCalled() + expect(queryClient.getQueryData(queryKey)).toEqual(initialData) + } finally { + await collection.cleanup() + } + }) + + it(`evaluates a function initializer once for a missing Query cache entry`, async () => { + const initialData = vi.fn(() => [{ id: `1`, name: `Initial item` }]) + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-function`, + queryClient, + queryKey: [`initial-data-function`], + queryFn: vi.fn().mockResolvedValue([]), + getKey, + startSync: true, + initialData, + staleTime: Infinity, + }), + ) + + try { + await vi.waitFor(() => { + expect(collection.get(`1`)?.name).toBe(`Initial item`) + }) + expect(initialData).toHaveBeenCalledTimes(1) + } finally { + await collection.cleanup() + } + }) + + it(`keeps stale initial rows while fetching and reconciles the server result`, async () => { + let resolveQuery: ((items: Array) => void) | undefined + const queryFn = vi.fn( + () => + new Promise>((resolve) => { + resolveQuery = resolve + }), + ) + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-stale`, + queryClient, + queryKey: [`initial-data-stale`], + queryFn, + getKey, + startSync: true, + initialData: [{ id: `initial`, name: `Initial` }], + initialDataUpdatedAt: 1, + staleTime: 0, + }), + ) + + try { + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.get(`initial`)?.name).toBe(`Initial`) + }) + + resolveQuery?.([{ id: `server`, name: `Server` }]) + await vi.waitFor(() => { + expect(collection.has(`initial`)).toBe(false) + expect(collection.get(`server`)?.name).toBe(`Server`) + }) + } finally { + await collection.cleanup() + } + }) + + it(`retains initial rows when a refetch fails`, async () => { + const initialRow = { id: `initial`, name: `Initial` } + const error = new Error(`Refetch failed`) + const queryFn = vi.fn().mockRejectedValue(error) + const consoleErrorSpy = vi + .spyOn(console, `error`) + .mockImplementation(() => {}) + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-refetch-error`, + queryClient, + queryKey: [`initial-data-refetch-error`], + queryFn, + getKey, + startSync: true, + initialData: [initialRow], + initialDataUpdatedAt: 1, + staleTime: 0, + retry: false, + }), + ) + + try { + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.utils.lastError).toBe(error) + }) + expect(stripVirtualProps(collection.get(initialRow.id))).toEqual( + initialRow, + ) + } finally { + consoleErrorSpy.mockRestore() + await collection.cleanup() + } + }) + + it(`ignores a late refetch result after initial data is cleaned up`, async () => { + const serverResult = createDeferred>() + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-late-cleanup`, + queryClient, + queryKey: [`initial-data-late-cleanup`], + queryFn: () => serverResult.promise, + getKey, + startSync: true, + initialData: [{ id: `initial`, name: `Initial` }], + initialDataUpdatedAt: 1, + staleTime: 0, + }), + ) + + await vi.waitFor(() => { + expect(collection.get(`initial`)?.name).toBe(`Initial`) + expect(queryClient.isFetching()).toBe(1) + }) + await collection.cleanup() + + serverResult.resolve([{ id: `server`, name: `Server` }]) + await vi.waitFor(() => expect(queryClient.isFetching()).toBe(0)) + expect(collection.status).toBe(`cleaned-up`) + expect(collection.size).toBe(0) + }) + + it(`projects a wrapped initial response while preserving its Query cache shape`, async () => { + const queryKey = [`initial-data-wrapped`] + const initialResponse = { + items: [{ id: `1`, name: `Initial item` }], + nextCursor: `next`, + } + const queryFn = vi.fn().mockResolvedValue(initialResponse) + + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-wrapped`, + queryClient, + queryKey, + queryFn, + select: (response: typeof initialResponse) => response.items, + getKey, + startSync: true, + initialData: initialResponse, + staleTime: Infinity, + }), + ) + + try { + await vi.waitFor(() => { + expect(stripVirtualProps(collection.get(`1`))).toEqual( + initialResponse.items[0], + ) + }) + + expect(queryFn).not.toHaveBeenCalled() + expect(queryClient.getQueryData(queryKey)).toEqual(initialResponse) + } finally { + await collection.cleanup() + } + }) + + it(`preserves a wrapped initial response when writing rows directly`, async () => { + const queryKey = [`initial-data-wrapped-writes`] + const initialResponse = { + items: [{ id: `1`, name: `Initial item` }], + nextCursor: `next`, + } + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-wrapped-writes`, + queryClient, + queryKey, + queryFn: vi.fn().mockResolvedValue(initialResponse), + select: (response: typeof initialResponse) => response.items, + getKey, + startSync: true, + initialData: initialResponse, + staleTime: Infinity, + }), + ) + + try { + await vi.waitFor(() => { + expect(collection.get(`1`)?.name).toBe(`Initial item`) + }) + + collection.utils.writeInsert({ id: `2`, name: `Inserted item` }) + collection.utils.writeUpdate({ id: `1`, name: `Updated item` }) + + expect(queryClient.getQueryData(queryKey)).toEqual({ + items: [ + { id: `1`, name: `Updated item` }, + { id: `2`, name: `Inserted item` }, + ], + nextCursor: `next`, + }) + } finally { + await collection.cleanup() + } + }) + + it(`keeps initial data scoped to each collection on a shared QueryClient`, async () => { + const first = createCollection( + queryCollectionOptions({ + id: `initial-data-shared-client-first`, + queryClient, + queryKey: [`initial-data-shared-client`, `first`], + queryFn: vi.fn().mockResolvedValue([]), + getKey, + startSync: true, + initialData: [{ id: `1`, name: `First` }], + staleTime: Infinity, + }), + ) + const second = createCollection( + queryCollectionOptions({ + id: `initial-data-shared-client-second`, + queryClient, + queryKey: [`initial-data-shared-client`, `second`], + queryFn: vi.fn().mockResolvedValue([]), + getKey, + startSync: true, + initialData: [{ id: `2`, name: `Second` }], + staleTime: Infinity, + }), + ) + + try { + await vi.waitFor(() => { + expect(first.get(`1`)?.name).toBe(`First`) + expect(second.get(`2`)?.name).toBe(`Second`) + }) + expect(first.has(`2`)).toBe(false) + expect(second.has(`1`)).toBe(false) + } finally { + await Promise.all([first.cleanup(), second.cleanup()]) + } + }) + + it(`does not replace existing Query data with a later collection initializer`, async () => { + const queryKey = [`initial-data-shared-key`] + queryClient.setQueryData(queryKey, [{ id: `cached`, name: `Cached` }]) + + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-shared-key`, + queryClient, + queryKey, + queryFn: vi.fn().mockResolvedValue([]), + getKey, + startSync: true, + initialData: [{ id: `initial`, name: `Initial` }], + staleTime: Infinity, + }), + ) + + try { + await vi.waitFor(() => { + expect(collection.get(`cached`)?.name).toBe(`Cached`) + }) + expect(collection.has(`initial`)).toBe(false) + } finally { + await collection.cleanup() + } + }) + + it(`rejects collection-level initial data in on-demand mode`, () => { + expect(() => + queryCollectionOptions({ + id: `initial-data-on-demand`, + queryClient, + queryKey: [`initial-data-on-demand`], + queryFn: vi.fn().mockResolvedValue([]), + getKey, + syncMode: `on-demand`, + initialData: [{ id: `1`, name: `Initial` }], + }), + ).toThrow( + `initialData and initialDataUpdatedAt are only supported when syncMode is 'eager'`, + ) + }) + + it(`does not apply QueryClient initial data defaults to on-demand subsets`, async () => { + const defaultInitialQueryClient = new QueryClient({ + defaultOptions: { + queries: { + initialData: [{ id: `default`, name: `Default` }], + staleTime: Infinity, + retry: false, + }, + }, + }) + const queryFn = vi + .fn() + .mockResolvedValue([{ id: `server`, name: `Server` }]) + const collection = createCollection( + queryCollectionOptions({ + id: `initial-data-default-on-demand`, + queryClient: defaultInitialQueryClient, + queryKey: [`initial-data-default-on-demand`], + queryFn, + getKey, + syncMode: `on-demand`, + startSync: true, + }), + ) + + try { + await collection._sync.loadSubset({}) + await vi.waitFor(() => { + expect(collection.get(`server`)?.name).toBe(`Server`) + }) + expect(collection.has(`default`)).toBe(false) + expect(queryFn).toHaveBeenCalledTimes(1) + } finally { + await collection.cleanup() + defaultInitialQueryClient.clear() + } + }) + + it(`does not materialize QueryClient placeholder defaults`, async () => { + const placeholderQueryClient = new QueryClient({ + defaultOptions: { + queries: { + placeholderData: [{ id: `placeholder`, name: `Placeholder` }], + retry: false, + }, + }, + }) + let resolveQuery: ((items: Array) => void) | undefined + const queryFn = vi.fn( + () => + new Promise>((resolve) => { + resolveQuery = resolve + }), + ) + const collection = createCollection( + queryCollectionOptions({ + id: `placeholder-default`, + queryClient: placeholderQueryClient, + queryKey: [`placeholder-default`], + queryFn, + getKey, + startSync: true, + }), + ) + + try { + await vi.waitFor(() => expect(queryFn).toHaveBeenCalledTimes(1)) + expect(collection.has(`placeholder`)).toBe(false) + + resolveQuery?.([{ id: `server`, name: `Server` }]) + await vi.waitFor(() => { + expect(collection.get(`server`)?.name).toBe(`Server`) + }) + } finally { + await collection.cleanup() + placeholderQueryClient.clear() + } + }) + }) + it(`should refetch on focus and reconnect with standalone QueryClient`, async () => { const queryKey = [`query-options-event-refetch`] const queryFn = vi @@ -5618,6 +6016,89 @@ describe(`QueryCollection`, () => { }) }) + it(`should not let initial data satisfy persisted revalidation`, async () => { + const queryKey = [`persisted-initial-data-revalidation`] + const queryHash = hashKey(queryKey) + const retainedRow = { + id: `retained`, + name: `Retained`, + category: `A`, + } + const initialRow = { + id: `initial`, + name: `Initial`, + category: `A`, + } + const serverRow = { id: `server`, name: `Server`, category: `A` } + const serverResult = createDeferred>() + const queryFn = vi.fn(() => serverResult.promise) + const adapter = createPersistedQueryAdapter({ + rows: new Map([[retainedRow.id, retainedRow]]), + rowMetadata: new Map([ + [ + retainedRow.id, + { + queryCollection: { + owners: { + [queryHash]: true, + }, + }, + }, + ], + ]), + collectionMetadata: new Map([ + [ + `queryCollection:gc:${queryHash}`, + { + queryHash, + mode: `until-revalidated`, + }, + ], + ]), + }) + + const collection = createCollection( + persistedCollectionOptions({ + ...(queryCollectionOptions({ + id: `persisted-initial-data-revalidation`, + queryClient, + queryKey, + queryFn, + getKey: (item) => item.id, + initialData: [initialRow], + staleTime: Infinity, + syncMode: `eager`, + startSync: true, + }) as any), + persistence: { + adapter, + }, + }) as any, + ) + + try { + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + }) + expect(adapter.rows.has(retainedRow.id)).toBe(true) + expect(adapter.rows.has(initialRow.id)).toBe(false) + expect( + adapter.collectionMetadata.has(`queryCollection:gc:${queryHash}`), + ).toBe(true) + + serverResult.resolve([serverRow]) + await vi.waitFor(() => { + expect(adapter.rows.has(retainedRow.id)).toBe(false) + expect(adapter.rows.get(serverRow.id)).toEqual(serverRow) + expect( + adapter.collectionMetadata.has(`queryCollection:gc:${queryHash}`), + ).toBe(false) + }) + } finally { + await collection.cleanup() + } + }) + it(`should diff against retained query-owned rows on warm start`, async () => { const baseQueryKey = [`persisted-baseline-test`] const queryFn = vi.fn().mockResolvedValue([])