Skip to content

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Nov 5, 2025

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@tanstack/[email protected]

Minor Changes

  • Add QueryObserver state utilities and convert error utils to getters (#742)

    Exposes TanStack Query's QueryObserver state through QueryCollectionUtils, providing visibility into sync status beyond just error states. Also converts existing error state utilities from methods to getters for consistency with TanStack DB/Query patterns.

    Breaking Changes:

    • lastError(), isError(), and errorCount() are now getters instead of methods
      • Before: collection.utils.lastError()
      • After: collection.utils.lastError

    New Utilities:

    • isFetching - Check if query is currently fetching (initial or background)
    • isRefetching - Check if query is refetching in background
    • isLoading - Check if query is loading for first time
    • dataUpdatedAt - Get timestamp of last successful data update
    • fetchStatus - Get current fetch status ('fetching' | 'paused' | 'idle')

    Use Cases:

    • Show loading indicators during background refetches
    • Implement "Last updated X minutes ago" UI patterns
    • Better understanding of query sync behavior

    Example Usage:

    const collection = queryCollectionOptions({
      // ... config
    })
    
    // Check sync status
    if (collection.utils.isFetching) {
      console.log("Syncing with server...")
    }
    
    if (collection.utils.isRefetching) {
      console.log("Background refresh in progress")
    }
    
    // Show last update time
    const lastUpdate = new Date(collection.utils.dataUpdatedAt)
    console.log(`Last synced: ${lastUpdate.toLocaleTimeString()}`)
    
    // Check error state (now using getters)
    if (collection.utils.isError) {
      console.error("Sync failed:", collection.utils.lastError)
      console.log(`Failed ${collection.utils.errorCount} times`)
    }

Patch Changes

  • Fix dependency bundling issues by moving @tanstack/db to peerDependencies (#766)

    What Changed:

    Moved @tanstack/db from regular dependencies to peerDependencies in:

    • @tanstack/offline-transactions
    • @tanstack/query-db-collection

    Removed @opentelemetry/api dependency from @tanstack/offline-transactions.

    Why:

    These extension packages incorrectly declared @tanstack/db as both a regular dependency AND a peerDependency simultaneously. This caused lock files to develop conflicting versions, resulting in multiple instances of @tanstack/db being installed in consuming applications.

    The fix removes @tanstack/db from regular dependencies and keeps it only as a peerDependency. This ensures only one version of @tanstack/db is installed in the dependency tree, preventing version conflicts.

    For local development, @tanstack/db remains in devDependencies so the packages can be built and tested independently.

  • Updated dependencies [6c55e16, 7805afb, 1367756]:

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

  • Fix type inference for findOne() when used with join operations (#749)

    Previously, using findOne() with join operations (leftJoin, innerJoin, etc.) resulted in the query type being inferred as never, breaking TypeScript type checking:

    const query = useLiveQuery(
      (q) =>
        q
          .from({ todo: todoCollection })
          .leftJoin({ todoOptions: todoOptionsCollection }, ...)
          .findOne() // Type became 'never'
    )

    The Fix:

    Fixed the MergeContextWithJoinType type definition to conditionally include the singleResult property only when it's explicitly true, avoiding type conflicts when findOne() is called after joins:

    // Before (buggy):
    singleResult: TContext['singleResult'] extends true ? true : false
    
    // After (fixed):
    type PreserveSingleResultFlag<TFlag> = [TFlag] extends [true]
      ? { singleResult: true }
      : {}
    
    // Used as:
    } & PreserveSingleResultFlag<TContext['singleResult']>

    Why This Works:

    By using a conditional intersection that omits the property entirely when not needed, we avoid type conflicts. Intersecting {} & { singleResult: true } cleanly results in { singleResult: true }, whereas the previous approach created conflicting property types resulting in never. The tuple wrapper ([TFlag]) ensures robust behavior even if the flag type becomes a union in the future.

    Impact:

    • findOne() now works correctly with all join types
    • ✅ Type inference works properly in useLiveQuery and other contexts
    • ✅ Both findOne() before and after joins work correctly
    • ✅ All tests pass with no breaking changes (8 new type tests added)
  • Improve error messages for custom getKey with joined queries (#717)

    Enhanced DuplicateKeySyncError to provide context-aware guidance when duplicate keys occur with custom getKey and joined queries.

    The Issue:

    When using custom getKey with joins, duplicate keys can occur if the join produces multiple rows with the same key value. This is valid for 1:1 relationships but problematic for 1:many relationships, and the previous error message didn't explain what went wrong or how to fix it.

    What's New:

    When a duplicate key error occurs in a live query collection that uses both custom getKey and joins, the error message now:

    • Explains that joined queries can produce multiple rows with the same key
    • Suggests using a composite key in your getKey function
    • Provides concrete examples of solutions
    • Helps distinguish between correctly structured 1:1 joins vs problematic 1:many joins

    Example:

    // ✅ Valid - 1:1 relationship with unique keys
    const userProfiles = createLiveQueryCollection({
      query: (q) =>
        q
          .from({ profile: profiles })
          .join({ user: users }, ({ profile, user }) =>
            eq(profile.userId, user.id)
          ),
      getKey: (profile) => profile.id, // Each profile has unique ID
    })
    // ⚠️ Problematic - 1:many relationship with duplicate keys
    const userComments = createLiveQueryCollection({
      query: (q) =>
        q
          .from({ user: users })
          .join({ comment: comments }, ({ user, comment }) =>
            eq(user.id, comment.userId)
          ),
      getKey: (item) => item.userId, // Multiple comments share same userId!
    })
    
    // Enhanced error message:
    // "Cannot insert document with key "user1" from sync because it already exists.
    // This collection uses a custom getKey with joined queries. Joined queries can
    // produce multiple rows with the same key when relationships are not 1:1.
    // Consider: (1) using a composite key in your getKey function (e.g., `${item.key1}-${item.key2}`),
    // (2) ensuring your join produces unique rows per key, or (3) removing the
    // custom getKey to use the default composite key behavior."
  • Add QueryObserver state utilities and convert error utils to getters (#742)

    Exposes TanStack Query's QueryObserver state through QueryCollectionUtils, providing visibility into sync status beyond just error states. Also converts existing error state utilities from methods to getters for consistency with TanStack DB/Query patterns.

    Breaking Changes:

    • lastError(), isError(), and errorCount() are now getters instead of methods
      • Before: collection.utils.lastError()
      • After: collection.utils.lastError

    New Utilities:

    • isFetching - Check if query is currently fetching (initial or background)
    • isRefetching - Check if query is refetching in background
    • isLoading - Check if query is loading for first time
    • dataUpdatedAt - Get timestamp of last successful data update
    • fetchStatus - Get current fetch status ('fetching' | 'paused' | 'idle')

    Use Cases:

    • Show loading indicators during background refetches
    • Implement "Last updated X minutes ago" UI patterns
    • Better understanding of query sync behavior

    Example Usage:

    const collection = queryCollectionOptions({
      // ... config
    })
    
    // Check sync status
    if (collection.utils.isFetching) {
      console.log("Syncing with server...")
    }
    
    if (collection.utils.isRefetching) {
      console.log("Background refresh in progress")
    }
    
    // Show last update time
    const lastUpdate = new Date(collection.utils.dataUpdatedAt)
    console.log(`Last synced: ${lastUpdate.toLocaleTimeString()}`)
    
    // Check error state (now using getters)
    if (collection.utils.isError) {
      console.error("Sync failed:", collection.utils.lastError)
      console.log(`Failed ${collection.utils.errorCount} times`)
    }

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

  • Fix dependency bundling issues by moving @tanstack/db to peerDependencies (#766)

    What Changed:

    Moved @tanstack/db from regular dependencies to peerDependencies in:

    • @tanstack/offline-transactions
    • @tanstack/query-db-collection

    Removed @opentelemetry/api dependency from @tanstack/offline-transactions.

    Why:

    These extension packages incorrectly declared @tanstack/db as both a regular dependency AND a peerDependency simultaneously. This caused lock files to develop conflicting versions, resulting in multiple instances of @tanstack/db being installed in consuming applications.

    The fix removes @tanstack/db from regular dependencies and keeps it only as a peerDependency. This ensures only one version of @tanstack/db is installed in the dependency tree, preventing version conflicts.

    For local development, @tanstack/db remains in devDependencies so the packages can be built and tested independently.

  • Updated dependencies [6c55e16, 7805afb, 1367756]:

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

  • Fix flushSync error in Svelte 5 async compiler mode (#745)

    Previously, useLiveQuery threw an error when Svelte 5's async compiler mode was enabled:

    Uncaught Svelte error: flush_sync_in_effect
    Cannot use flushSync inside an effect
    

    This occurred because flushSync() was called inside the onFirstReady callback, which executes within a $effect block. Svelte 5's async compiler enforces a strict rule that flushSync() cannot be called inside effects, as documented at svelte.dev/e/flush_sync_in_effect.

    The Fix:

    Removed the unnecessary flushSync() call from the onFirstReady callback. Svelte 5's reactivity system automatically propagates state changes without needing synchronous flushing. This matches the pattern already used in Vue's implementation.

    Compatibility:

    • ✅ For users WITHOUT async mode (current default): Works as before
    • ✅ For users WITH async mode: Now works instead of throwing error
    • ✅ Future-proof: async mode will be default in Svelte 6
    • ✅ All 23 existing tests pass, confirming no regression

    How to enable async mode:

    // svelte.config.js
    export default {
      compilerOptions: {
        experimental: {
          async: true,
        },
      },
    }

    Fixes svelte-db: flushSync inside effect breaks Svelte 5 async compiler mode #744

  • Updated dependencies [6c55e16, 7805afb, 1367756]:

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

@tanstack/[email protected]

Patch Changes

@github-actions github-actions bot force-pushed the changeset-release/main branch 2 times, most recently from 5f8d63a to 07fb2f8 Compare November 5, 2025 18:39
@github-actions github-actions bot force-pushed the changeset-release/main branch from 07fb2f8 to e571e5d Compare November 5, 2025 18:44
@KyleAMathews KyleAMathews merged commit 07a880f into main Nov 5, 2025
@KyleAMathews KyleAMathews deleted the changeset-release/main branch November 5, 2025 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

svelte-db: flushSync inside effect breaks Svelte 5 async compiler mode

2 participants