feat: $pendingOperation virtual prop#1431
Open
marbemac wants to merge 6 commits intoTanStack:mainfrom
Open
Conversation
… type
Add a new virtual property $pendingOperation ('insert' | 'update' | 'delete' | null)
to every collection row. This tells consumers what type of optimistic mutation is
pending, enabling draft/review UIs that show git-style change indicators.
- Subscription-layer delete-to-update conversion for opted-in queries - Auto-detection of $pendingOperation in where clauses - Initial snapshot includes pending-delete items - Fix missing $pendingOperation in multi-group GROUP BY path - Tighten string|null to PendingOperationType in group-by compiler - Fix ?? vs !== undefined inconsistency for nullable $pendingOperation - Add GROUP BY tests for $pendingOperation aggregation
… memory cleanup - Fix stale $pendingOperation after rollback of optimistic delete by tracking converted delete values and converting rollback inserts to updates - Fix isRowSynced to check pendingOptimistic* maps for consistency with getPendingOperation (prevents contradictory $synced: true + $pendingOperation: 'delete') - Fix lazy source (join/subquery) pending deletes by merging child sourceWhereClauses into parent during query compilation - Fix convertedDeleteValues memory leak: clean up on sync-confirmed delete, truncate, and unsubscribe - Add computePendingOperation callback to enrichRowWithVirtualProps
Export expressionReferencesPendingOperation from collection-subscriber and use it in effect.ts buildSubscriptionOptions to auto-detect $pendingOperation references and set includePendingDeletes on the subscription.
Signed-off-by: Marc MacLeod <marbemac+gh@gmail.com>
Contributor
Author
|
One thing I considered but left out because wasn't sure if ya'll would be ok w a new API method. A .where(({ task }) =>
and(
eq(task.projectId, projectId),
or(isNull(task.$pendingOperation), not(isNull(task.$pendingOperation))),
),
)User could do: .where(({ task }) => eq(task.projectId, projectId))
.includePendingDeletes()OR alternative, just a simple exported util: .where(({ task }) => includeDeletedItems(eq(task.projectId, projectId)))If that's something you guys would consider, I can try and add it - don't think it'd be much code. |
More templates
@tanstack/angular-db
@tanstack/browser-db-sqlite-persistence
@tanstack/capacitor-db-sqlite-persistence
@tanstack/cloudflare-durable-objects-db-sqlite-persistence
@tanstack/db
@tanstack/db-ivm
@tanstack/db-sqlite-persistence-core
@tanstack/electric-db-collection
@tanstack/electron-db-sqlite-persistence
@tanstack/expo-db-sqlite-persistence
@tanstack/node-db-sqlite-persistence
@tanstack/offline-transactions
@tanstack/powersync-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/react-native-db-sqlite-persistence
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/tauri-db-sqlite-persistence
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 Changes
Adds a new
$pendingOperationvirtual property to collection rows ('insert' | 'update' | 'delete' | null). This tells you what type of optimistic mutation is pending for each row, which is useful for building draft/review UIs where you want to show git-style change indicators, amongst other things.Note, large-ish diff but most of it is new tests 😅.
The key feature: items deleted in a pending transaction can now stay visible in query results. By default nothing changes — deleted items still vanish from queries. But if your query references
$pendingOperationin a.where()clause, the implicit filter is disabled and you can see pending-delete items inline:Works with live queries,
createEffect, joins/subqueries, GROUP BY, ordered/paginated queries, and selective where clauses likenot(isNull($pendingOperation))for "show only pending changes" views.This was discussed with @samwillis beforehand — the approach is to keep deletes as deletes at the collection layer and convert them to updates at the subscription layer when opted in.
I also went ahead and built and linked into our actual app to test out the functionality. It's damn neat! For example, can show indicators inline for as yet uncommitted transaction. Imagine CRM app, and app with data grids w rows that can be updated/removed/etc, or really any productivity app that wants to allow humans and agents to safely make changes, and represent those changes inline in the application without having to change the data layer at all (note
RandAbadges on the files on the left, and the red bordered "install" that has been deleted but not yet "committed").Notable behavioral change
isRowSynced()now also checkspendingOptimisticUpsertsandpendingOptimisticDeletes, so$syncedisfalseduring the completed-but-awaiting-sync window. Previously it could briefly show$synced: truewhile a pending operation was still awaiting sync confirmation.✅ Checklist
pnpm test.🚀 Release Impact