@tanstack/db 0.5.32 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/change-events.d.cts +3 -2
- package/dist/cjs/collection/changes.cjs +13 -4
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/changes.d.cts +10 -1
- package/dist/cjs/collection/cleanup-queue.cjs +89 -0
- package/dist/cjs/collection/cleanup-queue.cjs.map +1 -0
- package/dist/cjs/collection/cleanup-queue.d.cts +30 -0
- package/dist/cjs/collection/events.cjs +14 -0
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +39 -1
- package/dist/cjs/collection/index.cjs +66 -28
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +49 -36
- package/dist/cjs/collection/indexes.cjs +211 -62
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/indexes.d.cts +27 -17
- package/dist/cjs/collection/lifecycle.cjs +5 -22
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.d.cts +0 -1
- package/dist/cjs/collection/mutations.cjs +18 -0
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/mutations.d.cts +1 -0
- package/dist/cjs/collection/state.cjs +381 -53
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +65 -1
- package/dist/cjs/collection/subscription.cjs +6 -0
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +4 -0
- package/dist/cjs/collection/sync.cjs +108 -1
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/collection/sync.d.cts +2 -0
- package/dist/cjs/collection/transaction-metadata.cjs +5 -0
- package/dist/cjs/collection/transaction-metadata.cjs.map +1 -0
- package/dist/cjs/collection/transaction-metadata.d.cts +1 -0
- package/dist/cjs/errors.cjs +8 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +24 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +12 -3
- package/dist/cjs/indexes/auto-index.cjs +13 -6
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs +0 -3
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +2 -6
- package/dist/cjs/indexes/basic-index.cjs +361 -0
- package/dist/cjs/indexes/basic-index.cjs.map +1 -0
- package/dist/cjs/indexes/basic-index.d.cts +102 -0
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +1 -1
- package/dist/cjs/indexes/index-options.d.cts +8 -9
- package/dist/cjs/indexes/index-registry.cjs +89 -0
- package/dist/cjs/indexes/index-registry.cjs.map +1 -0
- package/dist/cjs/indexes/index-registry.d.cts +61 -0
- package/dist/cjs/local-only.cjs +5 -0
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs +27 -11
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +25 -3
- package/dist/cjs/query/builder/index.cjs +200 -39
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +4 -3
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +14 -3
- package/dist/cjs/query/builder/types.d.cts +84 -19
- package/dist/cjs/query/compiler/evaluators.cjs +51 -0
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +100 -28
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.d.cts +4 -2
- package/dist/cjs/query/compiler/index.cjs +283 -11
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +30 -2
- package/dist/cjs/query/compiler/order-by.cjs +29 -10
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -1
- package/dist/cjs/query/compiler/select.cjs +8 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/effect.cjs +602 -0
- package/dist/cjs/query/effect.cjs.map +1 -0
- package/dist/cjs/query/effect.d.cts +94 -0
- package/dist/cjs/query/index.d.cts +2 -1
- package/dist/cjs/query/ir.cjs +18 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +21 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +493 -66
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +33 -100
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +0 -1
- package/dist/cjs/query/live/types.d.cts +3 -3
- package/dist/cjs/query/live/utils.cjs +219 -0
- package/dist/cjs/query/live/utils.cjs.map +1 -0
- package/dist/cjs/query/live/utils.d.cts +110 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +9 -6
- package/dist/cjs/query/query-once.cjs.map +1 -1
- package/dist/cjs/query/query-once.d.cts +7 -5
- package/dist/cjs/query/subset-dedupe.cjs +9 -3
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/types.d.cts +42 -8
- package/dist/cjs/utils/array-utils.cjs +27 -0
- package/dist/cjs/utils/array-utils.cjs.map +1 -0
- package/dist/cjs/utils/array-utils.d.cts +16 -0
- package/dist/cjs/utils/comparison.cjs +11 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.cjs +4 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs +7 -9
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +6 -1
- package/dist/cjs/virtual-props.cjs +33 -0
- package/dist/cjs/virtual-props.cjs.map +1 -0
- package/dist/cjs/virtual-props.d.cts +196 -0
- package/dist/esm/collection/change-events.d.ts +3 -2
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.d.ts +10 -1
- package/dist/esm/collection/changes.js +13 -4
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/cleanup-queue.d.ts +30 -0
- package/dist/esm/collection/cleanup-queue.js +89 -0
- package/dist/esm/collection/cleanup-queue.js.map +1 -0
- package/dist/esm/collection/events.d.ts +39 -1
- package/dist/esm/collection/events.js +14 -0
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +49 -36
- package/dist/esm/collection/index.js +67 -29
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.d.ts +27 -17
- package/dist/esm/collection/indexes.js +211 -62
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.d.ts +0 -1
- package/dist/esm/collection/lifecycle.js +5 -22
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.d.ts +1 -0
- package/dist/esm/collection/mutations.js +18 -0
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +65 -1
- package/dist/esm/collection/state.js +381 -53
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +4 -0
- package/dist/esm/collection/subscription.js +6 -0
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.d.ts +2 -0
- package/dist/esm/collection/sync.js +108 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/collection/transaction-metadata.d.ts +1 -0
- package/dist/esm/collection/transaction-metadata.js +5 -0
- package/dist/esm/collection/transaction-metadata.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +8 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +12 -3
- package/dist/esm/index.js +27 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +13 -6
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +2 -6
- package/dist/esm/indexes/base-index.js +1 -4
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/basic-index.d.ts +102 -0
- package/dist/esm/indexes/basic-index.js +361 -0
- package/dist/esm/indexes/basic-index.js.map +1 -0
- package/dist/esm/indexes/btree-index.d.ts +1 -1
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/index-options.d.ts +8 -9
- package/dist/esm/indexes/index-registry.d.ts +61 -0
- package/dist/esm/indexes/index-registry.js +89 -0
- package/dist/esm/indexes/index-registry.js.map +1 -0
- package/dist/esm/local-only.js +5 -0
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +25 -3
- package/dist/esm/query/builder/functions.js +27 -11
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.d.ts +4 -3
- package/dist/esm/query/builder/index.js +201 -40
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +84 -19
- package/dist/esm/query/compiler/evaluators.js +51 -0
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.d.ts +4 -2
- package/dist/esm/query/compiler/group-by.js +101 -29
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +30 -2
- package/dist/esm/query/compiler/index.js +285 -13
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -1
- package/dist/esm/query/compiler/order-by.js +30 -11
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js +8 -0
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/effect.d.ts +94 -0
- package/dist/esm/query/effect.js +602 -0
- package/dist/esm/query/effect.js.map +1 -0
- package/dist/esm/query/index.d.ts +2 -1
- package/dist/esm/query/ir.d.ts +21 -1
- package/dist/esm/query/ir.js +18 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
- package/dist/esm/query/live/collection-config-builder.js +492 -65
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +0 -1
- package/dist/esm/query/live/collection-subscriber.js +31 -98
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +3 -3
- package/dist/esm/query/live/utils.d.ts +110 -0
- package/dist/esm/query/live/utils.js +219 -0
- package/dist/esm/query/live/utils.js.map +1 -0
- package/dist/esm/query/live-query-collection.d.ts +9 -6
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/query-once.d.ts +7 -5
- package/dist/esm/query/query-once.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js +9 -3
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/types.d.ts +42 -8
- package/dist/esm/utils/array-utils.d.ts +16 -0
- package/dist/esm/utils/array-utils.js +27 -0
- package/dist/esm/utils/array-utils.js.map +1 -0
- package/dist/esm/utils/comparison.js +11 -0
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/index-optimization.js +4 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.d.ts +6 -1
- package/dist/esm/utils.js +7 -9
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/virtual-props.d.ts +196 -0
- package/dist/esm/virtual-props.js +33 -0
- package/dist/esm/virtual-props.js.map +1 -0
- package/package.json +2 -2
- package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
- package/src/collection/change-events.ts +13 -9
- package/src/collection/changes.ts +30 -7
- package/src/collection/cleanup-queue.ts +105 -0
- package/src/collection/events.ts +65 -0
- package/src/collection/index.ts +110 -45
- package/src/collection/indexes.ts +283 -76
- package/src/collection/lifecycle.ts +5 -26
- package/src/collection/mutations.ts +21 -0
- package/src/collection/state.ts +545 -71
- package/src/collection/subscription.ts +7 -0
- package/src/collection/sync.ts +137 -0
- package/src/collection/transaction-metadata.ts +1 -0
- package/src/errors.ts +9 -0
- package/src/index.ts +57 -3
- package/src/indexes/auto-index.ts +18 -8
- package/src/indexes/base-index.ts +2 -10
- package/src/indexes/basic-index.ts +507 -0
- package/src/indexes/btree-index.ts +1 -1
- package/src/indexes/index-options.ts +17 -37
- package/src/indexes/index-registry.ts +174 -0
- package/src/local-only.ts +7 -0
- package/src/query/builder/functions.ts +84 -7
- package/src/query/builder/index.ts +329 -9
- package/src/query/builder/ref-proxy.ts +22 -4
- package/src/query/builder/types.ts +257 -62
- package/src/query/compiler/evaluators.ts +57 -0
- package/src/query/compiler/group-by.ts +156 -35
- package/src/query/compiler/index.ts +445 -15
- package/src/query/compiler/order-by.ts +51 -12
- package/src/query/compiler/select.ts +9 -0
- package/src/query/effect.ts +1119 -0
- package/src/query/index.ts +7 -0
- package/src/query/ir.ts +23 -2
- package/src/query/live/collection-config-builder.ts +778 -104
- package/src/query/live/collection-subscriber.ts +40 -156
- package/src/query/live/types.ts +10 -4
- package/src/query/live/utils.ts +417 -0
- package/src/query/live-query-collection.ts +43 -18
- package/src/query/query-once.ts +31 -12
- package/src/query/subset-dedupe.ts +11 -7
- package/src/types.ts +49 -9
- package/src/utils/array-utils.ts +49 -0
- package/src/utils/comparison.ts +14 -0
- package/src/utils/index-optimization.ts +4 -0
- package/src/utils.ts +12 -9
- package/src/virtual-props.ts +282 -0
- package/dist/cjs/indexes/lazy-index.cjs +0 -190
- package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
- package/dist/cjs/indexes/lazy-index.d.cts +0 -96
- package/dist/esm/indexes/lazy-index.d.ts +0 -96
- package/dist/esm/indexes/lazy-index.js +0 -190
- package/dist/esm/indexes/lazy-index.js.map +0 -1
- package/src/indexes/lazy-index.ts +0 -251
package/src/collection/state.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { deepEquals } from '../utils'
|
|
2
2
|
import { SortedMap } from '../SortedMap'
|
|
3
|
+
import { enrichRowWithVirtualProps } from '../virtual-props.js'
|
|
4
|
+
import { DIRECT_TRANSACTION_METADATA_KEY } from './transaction-metadata.js'
|
|
5
|
+
import type {
|
|
6
|
+
VirtualOrigin,
|
|
7
|
+
VirtualRowProps,
|
|
8
|
+
WithVirtualProps,
|
|
9
|
+
} from '../virtual-props.js'
|
|
3
10
|
import type { Transaction } from '../transactions'
|
|
4
11
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
5
12
|
import type {
|
|
@@ -21,6 +28,8 @@ interface PendingSyncedTransaction<
|
|
|
21
28
|
operations: Array<OptimisticChangeMessage<T>>
|
|
22
29
|
truncate?: boolean
|
|
23
30
|
deletedKeys: Set<string | number>
|
|
31
|
+
rowMetadataWrites: Map<TKey, PendingMetadataWrite>
|
|
32
|
+
collectionMetadataWrites: Map<string, PendingMetadataWrite>
|
|
24
33
|
optimisticSnapshot?: {
|
|
25
34
|
upserts: Map<TKey, T>
|
|
26
35
|
deletes: Set<TKey>
|
|
@@ -33,6 +42,18 @@ interface PendingSyncedTransaction<
|
|
|
33
42
|
immediate?: boolean
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
type PendingMetadataWrite = { type: `set`; value: unknown } | { type: `delete` }
|
|
46
|
+
|
|
47
|
+
type InternalChangeMessage<
|
|
48
|
+
T extends object = Record<string, unknown>,
|
|
49
|
+
TKey extends string | number = string | number,
|
|
50
|
+
> = ChangeMessage<T, TKey> & {
|
|
51
|
+
__virtualProps?: {
|
|
52
|
+
value?: VirtualRowProps<TKey>
|
|
53
|
+
previousValue?: VirtualRowProps<TKey>
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
export class CollectionStateManager<
|
|
37
58
|
TOutput extends object = Record<string, unknown>,
|
|
38
59
|
TKey extends string | number = string | number,
|
|
@@ -53,10 +74,45 @@ export class CollectionStateManager<
|
|
|
53
74
|
> = []
|
|
54
75
|
public syncedData: SortedMap<TKey, TOutput>
|
|
55
76
|
public syncedMetadata = new Map<TKey, unknown>()
|
|
77
|
+
public syncedCollectionMetadata = new Map<string, unknown>()
|
|
56
78
|
|
|
57
79
|
// Optimistic state tracking - make public for testing
|
|
58
80
|
public optimisticUpserts = new Map<TKey, TOutput>()
|
|
59
81
|
public optimisticDeletes = new Set<TKey>()
|
|
82
|
+
public pendingOptimisticUpserts = new Map<TKey, TOutput>()
|
|
83
|
+
public pendingOptimisticDeletes = new Set<TKey>()
|
|
84
|
+
public pendingOptimisticDirectUpserts = new Set<TKey>()
|
|
85
|
+
public pendingOptimisticDirectDeletes = new Set<TKey>()
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Tracks the origin of confirmed changes for each row.
|
|
89
|
+
* 'local' = change originated from this client
|
|
90
|
+
* 'remote' = change was received via sync
|
|
91
|
+
*
|
|
92
|
+
* This is used for the $origin virtual property.
|
|
93
|
+
* Note: This only tracks *confirmed* changes, not optimistic ones.
|
|
94
|
+
* Optimistic changes are always considered 'local' for $origin.
|
|
95
|
+
*/
|
|
96
|
+
public rowOrigins = new Map<TKey, VirtualOrigin>()
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Tracks keys that have pending local changes.
|
|
100
|
+
* Used to determine whether sync-confirmed data should have 'local' or 'remote' origin.
|
|
101
|
+
* When sync confirms data for a key with pending local changes, it keeps 'local' origin.
|
|
102
|
+
*/
|
|
103
|
+
public pendingLocalChanges = new Set<TKey>()
|
|
104
|
+
public pendingLocalOrigins = new Set<TKey>()
|
|
105
|
+
|
|
106
|
+
private virtualPropsCache = new WeakMap<
|
|
107
|
+
object,
|
|
108
|
+
{
|
|
109
|
+
synced: boolean
|
|
110
|
+
origin: VirtualOrigin
|
|
111
|
+
key: TKey
|
|
112
|
+
collectionId: string
|
|
113
|
+
enriched: WithVirtualProps<TOutput, TKey>
|
|
114
|
+
}
|
|
115
|
+
>()
|
|
60
116
|
|
|
61
117
|
// Cached size for performance
|
|
62
118
|
public size = 0
|
|
@@ -67,6 +123,7 @@ export class CollectionStateManager<
|
|
|
67
123
|
public recentlySyncedKeys = new Set<TKey>()
|
|
68
124
|
public hasReceivedFirstCommit = false
|
|
69
125
|
public isCommittingSyncTransactions = false
|
|
126
|
+
public isLocalOnly = false
|
|
70
127
|
|
|
71
128
|
/**
|
|
72
129
|
* Creates a new CollectionState manager
|
|
@@ -96,6 +153,183 @@ export class CollectionStateManager<
|
|
|
96
153
|
this._events = deps.events
|
|
97
154
|
}
|
|
98
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Checks if a row has pending optimistic mutations (not yet confirmed by sync).
|
|
158
|
+
* Used to compute the $synced virtual property.
|
|
159
|
+
*/
|
|
160
|
+
public isRowSynced(key: TKey): boolean {
|
|
161
|
+
if (this.isLocalOnly) {
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
return !this.optimisticUpserts.has(key) && !this.optimisticDeletes.has(key)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Gets the origin of the last confirmed change to a row.
|
|
169
|
+
* Returns 'local' if the row has optimistic mutations (optimistic changes are local).
|
|
170
|
+
* Used to compute the $origin virtual property.
|
|
171
|
+
*/
|
|
172
|
+
public getRowOrigin(key: TKey): VirtualOrigin {
|
|
173
|
+
if (this.isLocalOnly) {
|
|
174
|
+
return 'local'
|
|
175
|
+
}
|
|
176
|
+
// If there are optimistic changes, they're local
|
|
177
|
+
if (this.optimisticUpserts.has(key) || this.optimisticDeletes.has(key)) {
|
|
178
|
+
return 'local'
|
|
179
|
+
}
|
|
180
|
+
// Otherwise, return the confirmed origin (defaults to 'remote' for synced data)
|
|
181
|
+
return this.rowOrigins.get(key) ?? 'remote'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private createVirtualPropsSnapshot(
|
|
185
|
+
key: TKey,
|
|
186
|
+
overrides?: Partial<VirtualRowProps<TKey>>,
|
|
187
|
+
): VirtualRowProps<TKey> {
|
|
188
|
+
return {
|
|
189
|
+
$synced: overrides?.$synced ?? this.isRowSynced(key),
|
|
190
|
+
$origin: overrides?.$origin ?? this.getRowOrigin(key),
|
|
191
|
+
$key: overrides?.$key ?? key,
|
|
192
|
+
$collectionId: overrides?.$collectionId ?? this.collection.id,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private getVirtualPropsSnapshotForState(
|
|
197
|
+
key: TKey,
|
|
198
|
+
options?: {
|
|
199
|
+
rowOrigins?: ReadonlyMap<TKey, VirtualOrigin>
|
|
200
|
+
optimisticUpserts?: Pick<Map<TKey, unknown>, 'has'>
|
|
201
|
+
optimisticDeletes?: Pick<Set<TKey>, 'has'>
|
|
202
|
+
completedOptimisticKeys?: Pick<Map<TKey, unknown>, 'has'>
|
|
203
|
+
},
|
|
204
|
+
): VirtualRowProps<TKey> {
|
|
205
|
+
if (this.isLocalOnly) {
|
|
206
|
+
return this.createVirtualPropsSnapshot(key, {
|
|
207
|
+
$synced: true,
|
|
208
|
+
$origin: 'local',
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const optimisticUpserts =
|
|
213
|
+
options?.optimisticUpserts ?? this.optimisticUpserts
|
|
214
|
+
const optimisticDeletes =
|
|
215
|
+
options?.optimisticDeletes ?? this.optimisticDeletes
|
|
216
|
+
const hasOptimisticChange =
|
|
217
|
+
optimisticUpserts.has(key) ||
|
|
218
|
+
optimisticDeletes.has(key) ||
|
|
219
|
+
options?.completedOptimisticKeys?.has(key) === true
|
|
220
|
+
|
|
221
|
+
return this.createVirtualPropsSnapshot(key, {
|
|
222
|
+
$synced: !hasOptimisticChange,
|
|
223
|
+
$origin: hasOptimisticChange
|
|
224
|
+
? 'local'
|
|
225
|
+
: ((options?.rowOrigins ?? this.rowOrigins).get(key) ?? 'remote'),
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private enrichWithVirtualPropsSnapshot(
|
|
230
|
+
row: TOutput,
|
|
231
|
+
virtualProps: VirtualRowProps<TKey>,
|
|
232
|
+
): WithVirtualProps<TOutput, TKey> {
|
|
233
|
+
const existingRow = row as Partial<WithVirtualProps<TOutput, TKey>>
|
|
234
|
+
const synced = existingRow.$synced ?? virtualProps.$synced
|
|
235
|
+
const origin = existingRow.$origin ?? virtualProps.$origin
|
|
236
|
+
const resolvedKey = existingRow.$key ?? virtualProps.$key
|
|
237
|
+
const collectionId = existingRow.$collectionId ?? virtualProps.$collectionId
|
|
238
|
+
|
|
239
|
+
const cached = this.virtualPropsCache.get(row as object)
|
|
240
|
+
if (
|
|
241
|
+
cached &&
|
|
242
|
+
cached.synced === synced &&
|
|
243
|
+
cached.origin === origin &&
|
|
244
|
+
cached.key === resolvedKey &&
|
|
245
|
+
cached.collectionId === collectionId
|
|
246
|
+
) {
|
|
247
|
+
return cached.enriched
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const enriched = {
|
|
251
|
+
...row,
|
|
252
|
+
$synced: synced,
|
|
253
|
+
$origin: origin,
|
|
254
|
+
$key: resolvedKey,
|
|
255
|
+
$collectionId: collectionId,
|
|
256
|
+
} as WithVirtualProps<TOutput, TKey>
|
|
257
|
+
|
|
258
|
+
this.virtualPropsCache.set(row as object, {
|
|
259
|
+
synced,
|
|
260
|
+
origin,
|
|
261
|
+
key: resolvedKey,
|
|
262
|
+
collectionId,
|
|
263
|
+
enriched,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
return enriched
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private clearOriginTrackingState(): void {
|
|
270
|
+
this.rowOrigins.clear()
|
|
271
|
+
this.pendingLocalChanges.clear()
|
|
272
|
+
this.pendingLocalOrigins.clear()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Enriches a row with virtual properties using the "add-if-missing" pattern.
|
|
277
|
+
* If the row already has virtual properties (from an upstream collection),
|
|
278
|
+
* they are preserved. Otherwise, new values are computed.
|
|
279
|
+
*/
|
|
280
|
+
public enrichWithVirtualProps(
|
|
281
|
+
row: TOutput,
|
|
282
|
+
key: TKey,
|
|
283
|
+
): WithVirtualProps<TOutput, TKey> {
|
|
284
|
+
return this.enrichWithVirtualPropsSnapshot(
|
|
285
|
+
row,
|
|
286
|
+
this.createVirtualPropsSnapshot(key),
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Creates a change message with virtual properties.
|
|
292
|
+
* Uses the "add-if-missing" pattern so that pass-through from upstream
|
|
293
|
+
* collections works correctly.
|
|
294
|
+
*/
|
|
295
|
+
public enrichChangeMessage(
|
|
296
|
+
change: ChangeMessage<TOutput, TKey>,
|
|
297
|
+
): ChangeMessage<WithVirtualProps<TOutput, TKey>, TKey> {
|
|
298
|
+
const { __virtualProps } = change as InternalChangeMessage<TOutput, TKey>
|
|
299
|
+
const enrichedValue = __virtualProps?.value
|
|
300
|
+
? this.enrichWithVirtualPropsSnapshot(change.value, __virtualProps.value)
|
|
301
|
+
: this.enrichWithVirtualProps(change.value, change.key)
|
|
302
|
+
const enrichedPreviousValue = change.previousValue
|
|
303
|
+
? __virtualProps?.previousValue
|
|
304
|
+
? this.enrichWithVirtualPropsSnapshot(
|
|
305
|
+
change.previousValue,
|
|
306
|
+
__virtualProps.previousValue,
|
|
307
|
+
)
|
|
308
|
+
: this.enrichWithVirtualProps(change.previousValue, change.key)
|
|
309
|
+
: undefined
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
key: change.key,
|
|
313
|
+
type: change.type,
|
|
314
|
+
value: enrichedValue,
|
|
315
|
+
previousValue: enrichedPreviousValue,
|
|
316
|
+
metadata: change.metadata,
|
|
317
|
+
} as ChangeMessage<WithVirtualProps<TOutput, TKey>, TKey>
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get the current value for a key enriched with virtual properties.
|
|
322
|
+
*/
|
|
323
|
+
public getWithVirtualProps(
|
|
324
|
+
key: TKey,
|
|
325
|
+
): WithVirtualProps<TOutput, TKey> | undefined {
|
|
326
|
+
const value = this.get(key)
|
|
327
|
+
if (value === undefined) {
|
|
328
|
+
return undefined
|
|
329
|
+
}
|
|
330
|
+
return this.enrichWithVirtualProps(value, key)
|
|
331
|
+
}
|
|
332
|
+
|
|
99
333
|
/**
|
|
100
334
|
* Get the current value for a key (virtual derived state)
|
|
101
335
|
*/
|
|
@@ -242,10 +476,108 @@ export class CollectionStateManager<
|
|
|
242
476
|
|
|
243
477
|
const previousState = new Map(this.optimisticUpserts)
|
|
244
478
|
const previousDeletes = new Set(this.optimisticDeletes)
|
|
479
|
+
const previousRowOrigins = new Map(this.rowOrigins)
|
|
480
|
+
|
|
481
|
+
// Update pending optimistic state for completed/failed transactions
|
|
482
|
+
for (const transaction of this.transactions.values()) {
|
|
483
|
+
const isDirectTransaction =
|
|
484
|
+
transaction.metadata[DIRECT_TRANSACTION_METADATA_KEY] === true
|
|
485
|
+
if (transaction.state === `completed`) {
|
|
486
|
+
for (const mutation of transaction.mutations) {
|
|
487
|
+
if (!this.isThisCollection(mutation.collection)) {
|
|
488
|
+
continue
|
|
489
|
+
}
|
|
490
|
+
this.pendingLocalOrigins.add(mutation.key)
|
|
491
|
+
if (!mutation.optimistic) {
|
|
492
|
+
continue
|
|
493
|
+
}
|
|
494
|
+
switch (mutation.type) {
|
|
495
|
+
case `insert`:
|
|
496
|
+
case `update`:
|
|
497
|
+
this.pendingOptimisticUpserts.set(
|
|
498
|
+
mutation.key,
|
|
499
|
+
mutation.modified as TOutput,
|
|
500
|
+
)
|
|
501
|
+
this.pendingOptimisticDeletes.delete(mutation.key)
|
|
502
|
+
if (isDirectTransaction) {
|
|
503
|
+
this.pendingOptimisticDirectUpserts.add(mutation.key)
|
|
504
|
+
this.pendingOptimisticDirectDeletes.delete(mutation.key)
|
|
505
|
+
} else {
|
|
506
|
+
this.pendingOptimisticDirectUpserts.delete(mutation.key)
|
|
507
|
+
this.pendingOptimisticDirectDeletes.delete(mutation.key)
|
|
508
|
+
}
|
|
509
|
+
break
|
|
510
|
+
case `delete`:
|
|
511
|
+
this.pendingOptimisticUpserts.delete(mutation.key)
|
|
512
|
+
this.pendingOptimisticDeletes.add(mutation.key)
|
|
513
|
+
if (isDirectTransaction) {
|
|
514
|
+
this.pendingOptimisticDirectUpserts.delete(mutation.key)
|
|
515
|
+
this.pendingOptimisticDirectDeletes.add(mutation.key)
|
|
516
|
+
} else {
|
|
517
|
+
this.pendingOptimisticDirectUpserts.delete(mutation.key)
|
|
518
|
+
this.pendingOptimisticDirectDeletes.delete(mutation.key)
|
|
519
|
+
}
|
|
520
|
+
break
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} else if (transaction.state === `failed`) {
|
|
524
|
+
for (const mutation of transaction.mutations) {
|
|
525
|
+
if (!this.isThisCollection(mutation.collection)) {
|
|
526
|
+
continue
|
|
527
|
+
}
|
|
528
|
+
this.pendingLocalOrigins.delete(mutation.key)
|
|
529
|
+
if (mutation.optimistic) {
|
|
530
|
+
this.pendingOptimisticUpserts.delete(mutation.key)
|
|
531
|
+
this.pendingOptimisticDeletes.delete(mutation.key)
|
|
532
|
+
this.pendingOptimisticDirectUpserts.delete(mutation.key)
|
|
533
|
+
this.pendingOptimisticDirectDeletes.delete(mutation.key)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
245
538
|
|
|
246
539
|
// Clear current optimistic state
|
|
247
540
|
this.optimisticUpserts.clear()
|
|
248
541
|
this.optimisticDeletes.clear()
|
|
542
|
+
this.pendingLocalChanges.clear()
|
|
543
|
+
|
|
544
|
+
// Seed optimistic state with pending optimistic mutations only when a sync is pending
|
|
545
|
+
const pendingSyncKeys = new Set<TKey>()
|
|
546
|
+
for (const transaction of this.pendingSyncedTransactions) {
|
|
547
|
+
for (const operation of transaction.operations) {
|
|
548
|
+
pendingSyncKeys.add(operation.key as TKey)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const staleOptimisticUpserts: Array<TKey> = []
|
|
552
|
+
for (const [key, value] of this.pendingOptimisticUpserts) {
|
|
553
|
+
if (
|
|
554
|
+
pendingSyncKeys.has(key) ||
|
|
555
|
+
this.pendingOptimisticDirectUpserts.has(key)
|
|
556
|
+
) {
|
|
557
|
+
this.optimisticUpserts.set(key, value)
|
|
558
|
+
} else {
|
|
559
|
+
staleOptimisticUpserts.push(key)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
for (const key of staleOptimisticUpserts) {
|
|
563
|
+
this.pendingOptimisticUpserts.delete(key)
|
|
564
|
+
this.pendingLocalOrigins.delete(key)
|
|
565
|
+
}
|
|
566
|
+
const staleOptimisticDeletes: Array<TKey> = []
|
|
567
|
+
for (const key of this.pendingOptimisticDeletes) {
|
|
568
|
+
if (
|
|
569
|
+
pendingSyncKeys.has(key) ||
|
|
570
|
+
this.pendingOptimisticDirectDeletes.has(key)
|
|
571
|
+
) {
|
|
572
|
+
this.optimisticDeletes.add(key)
|
|
573
|
+
} else {
|
|
574
|
+
staleOptimisticDeletes.push(key)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
for (const key of staleOptimisticDeletes) {
|
|
578
|
+
this.pendingOptimisticDeletes.delete(key)
|
|
579
|
+
this.pendingLocalOrigins.delete(key)
|
|
580
|
+
}
|
|
249
581
|
|
|
250
582
|
const activeTransactions: Array<Transaction<any>> = []
|
|
251
583
|
|
|
@@ -258,7 +590,14 @@ export class CollectionStateManager<
|
|
|
258
590
|
// Apply active transactions only (completed transactions are handled by sync operations)
|
|
259
591
|
for (const transaction of activeTransactions) {
|
|
260
592
|
for (const mutation of transaction.mutations) {
|
|
261
|
-
if (this.isThisCollection(mutation.collection)
|
|
593
|
+
if (!this.isThisCollection(mutation.collection)) {
|
|
594
|
+
continue
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Track that this key has pending local changes for $origin tracking
|
|
598
|
+
this.pendingLocalChanges.add(mutation.key)
|
|
599
|
+
|
|
600
|
+
if (mutation.optimistic) {
|
|
262
601
|
switch (mutation.type) {
|
|
263
602
|
case `insert`:
|
|
264
603
|
case `update`:
|
|
@@ -281,8 +620,13 @@ export class CollectionStateManager<
|
|
|
281
620
|
this.size = this.calculateSize()
|
|
282
621
|
|
|
283
622
|
// Collect events for changes
|
|
284
|
-
const events: Array<
|
|
285
|
-
this.collectOptimisticChanges(
|
|
623
|
+
const events: Array<InternalChangeMessage<TOutput, TKey>> = []
|
|
624
|
+
this.collectOptimisticChanges(
|
|
625
|
+
previousState,
|
|
626
|
+
previousDeletes,
|
|
627
|
+
previousRowOrigins,
|
|
628
|
+
events,
|
|
629
|
+
)
|
|
286
630
|
|
|
287
631
|
// Filter out events for recently synced keys to prevent duplicates
|
|
288
632
|
// BUT: Only filter out events that are actually from sync operations
|
|
@@ -305,12 +649,12 @@ export class CollectionStateManager<
|
|
|
305
649
|
// that will immediately restore the same data, but only for completed transactions
|
|
306
650
|
// IMPORTANT: Skip complex filtering for user-triggered actions to prevent UI blocking
|
|
307
651
|
if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {
|
|
308
|
-
const
|
|
652
|
+
const pendingSyncKeysForFilter = new Set<TKey>()
|
|
309
653
|
|
|
310
654
|
// Collect keys from pending sync operations
|
|
311
655
|
for (const transaction of this.pendingSyncedTransactions) {
|
|
312
656
|
for (const operation of transaction.operations) {
|
|
313
|
-
|
|
657
|
+
pendingSyncKeysForFilter.add(operation.key as TKey)
|
|
314
658
|
}
|
|
315
659
|
}
|
|
316
660
|
|
|
@@ -318,7 +662,10 @@ export class CollectionStateManager<
|
|
|
318
662
|
// 1. Have pending sync operations AND
|
|
319
663
|
// 2. Are from completed transactions (being cleaned up)
|
|
320
664
|
const filteredEvents = filteredEventsBySyncStatus.filter((event) => {
|
|
321
|
-
if (
|
|
665
|
+
if (
|
|
666
|
+
event.type === `delete` &&
|
|
667
|
+
pendingSyncKeysForFilter.has(event.key)
|
|
668
|
+
) {
|
|
322
669
|
// Check if this delete is from clearing optimistic state of completed transactions
|
|
323
670
|
// We can infer this by checking if we have no remaining optimistic mutations for this key
|
|
324
671
|
const hasActiveOptimisticMutation = activeTransactions.some((tx) =>
|
|
@@ -370,7 +717,8 @@ export class CollectionStateManager<
|
|
|
370
717
|
private collectOptimisticChanges(
|
|
371
718
|
previousUpserts: Map<TKey, TOutput>,
|
|
372
719
|
previousDeletes: Set<TKey>,
|
|
373
|
-
|
|
720
|
+
previousRowOrigins: ReadonlyMap<TKey, VirtualOrigin>,
|
|
721
|
+
events: Array<InternalChangeMessage<TOutput, TKey>>,
|
|
374
722
|
): void {
|
|
375
723
|
const allKeys = new Set([
|
|
376
724
|
...previousUpserts.keys(),
|
|
@@ -386,11 +734,31 @@ export class CollectionStateManager<
|
|
|
386
734
|
previousUpserts,
|
|
387
735
|
previousDeletes,
|
|
388
736
|
)
|
|
737
|
+
const previousVirtualProps = this.getVirtualPropsSnapshotForState(key, {
|
|
738
|
+
rowOrigins: previousRowOrigins,
|
|
739
|
+
optimisticUpserts: previousUpserts,
|
|
740
|
+
optimisticDeletes: previousDeletes,
|
|
741
|
+
})
|
|
742
|
+
const nextVirtualProps = this.getVirtualPropsSnapshotForState(key)
|
|
389
743
|
|
|
390
744
|
if (previousValue !== undefined && currentValue === undefined) {
|
|
391
|
-
events.push({
|
|
745
|
+
events.push({
|
|
746
|
+
type: `delete`,
|
|
747
|
+
key,
|
|
748
|
+
value: previousValue,
|
|
749
|
+
__virtualProps: {
|
|
750
|
+
value: previousVirtualProps,
|
|
751
|
+
},
|
|
752
|
+
})
|
|
392
753
|
} else if (previousValue === undefined && currentValue !== undefined) {
|
|
393
|
-
events.push({
|
|
754
|
+
events.push({
|
|
755
|
+
type: `insert`,
|
|
756
|
+
key,
|
|
757
|
+
value: currentValue,
|
|
758
|
+
__virtualProps: {
|
|
759
|
+
value: nextVirtualProps,
|
|
760
|
+
},
|
|
761
|
+
})
|
|
394
762
|
} else if (
|
|
395
763
|
previousValue !== undefined &&
|
|
396
764
|
currentValue !== undefined &&
|
|
@@ -401,6 +769,10 @@ export class CollectionStateManager<
|
|
|
401
769
|
key,
|
|
402
770
|
value: currentValue,
|
|
403
771
|
previousValue,
|
|
772
|
+
__virtualProps: {
|
|
773
|
+
value: nextVirtualProps,
|
|
774
|
+
previousValue: previousVirtualProps,
|
|
775
|
+
},
|
|
404
776
|
})
|
|
405
777
|
}
|
|
406
778
|
}
|
|
@@ -485,11 +857,17 @@ export class CollectionStateManager<
|
|
|
485
857
|
// Set flag to prevent redundant optimistic state recalculations
|
|
486
858
|
this.isCommittingSyncTransactions = true
|
|
487
859
|
|
|
860
|
+
const previousRowOrigins = new Map(this.rowOrigins)
|
|
861
|
+
const previousOptimisticUpserts = new Map(this.optimisticUpserts)
|
|
862
|
+
const previousOptimisticDeletes = new Set(this.optimisticDeletes)
|
|
863
|
+
|
|
488
864
|
// Get the optimistic snapshot from the truncate transaction (captured when truncate() was called)
|
|
489
865
|
const truncateOptimisticSnapshot = hasTruncateSync
|
|
490
866
|
? committedSyncedTransactions.find((t) => t.truncate)
|
|
491
867
|
?.optimisticSnapshot
|
|
492
868
|
: null
|
|
869
|
+
let truncatePendingLocalChanges: Set<TKey> | undefined
|
|
870
|
+
let truncatePendingLocalOrigins: Set<TKey> | undefined
|
|
493
871
|
|
|
494
872
|
// First collect all keys that will be affected by sync operations
|
|
495
873
|
const changedKeys = new Set<TKey>()
|
|
@@ -497,6 +875,9 @@ export class CollectionStateManager<
|
|
|
497
875
|
for (const operation of transaction.operations) {
|
|
498
876
|
changedKeys.add(operation.key as TKey)
|
|
499
877
|
}
|
|
878
|
+
for (const [key] of transaction.rowMetadataWrites) {
|
|
879
|
+
changedKeys.add(key)
|
|
880
|
+
}
|
|
500
881
|
}
|
|
501
882
|
|
|
502
883
|
// Use pre-captured state if available (from optimistic scenarios),
|
|
@@ -515,6 +896,25 @@ export class CollectionStateManager<
|
|
|
515
896
|
|
|
516
897
|
const events: Array<ChangeMessage<TOutput, TKey>> = []
|
|
517
898
|
const rowUpdateMode = this.config.sync.rowUpdateMode || `partial`
|
|
899
|
+
const completedOptimisticOps = new Map<
|
|
900
|
+
TKey,
|
|
901
|
+
{ type: string; value: TOutput }
|
|
902
|
+
>()
|
|
903
|
+
|
|
904
|
+
for (const transaction of this.transactions.values()) {
|
|
905
|
+
if (transaction.state === `completed`) {
|
|
906
|
+
for (const mutation of transaction.mutations) {
|
|
907
|
+
if (this.isThisCollection(mutation.collection)) {
|
|
908
|
+
if (mutation.optimistic) {
|
|
909
|
+
completedOptimisticOps.set(mutation.key, {
|
|
910
|
+
type: mutation.type,
|
|
911
|
+
value: mutation.modified as TOutput,
|
|
912
|
+
})
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
518
918
|
|
|
519
919
|
for (const transaction of committedSyncedTransactions) {
|
|
520
920
|
// Handle truncate operations first
|
|
@@ -540,9 +940,14 @@ export class CollectionStateManager<
|
|
|
540
940
|
|
|
541
941
|
// 2) Clear the authoritative synced base. Subsequent server ops in this
|
|
542
942
|
// same commit will rebuild the base atomically.
|
|
943
|
+
// Preserve pending local tracking just long enough for operations in this
|
|
944
|
+
// truncate batch to retain correct local origin semantics.
|
|
945
|
+
truncatePendingLocalChanges = new Set(this.pendingLocalChanges)
|
|
946
|
+
truncatePendingLocalOrigins = new Set(this.pendingLocalOrigins)
|
|
543
947
|
this.syncedData.clear()
|
|
544
948
|
this.syncedMetadata.clear()
|
|
545
949
|
this.syncedKeys.clear()
|
|
950
|
+
this.clearOriginTrackingState()
|
|
546
951
|
|
|
547
952
|
// 3) Clear currentVisibleState for truncated keys to ensure subsequent operations
|
|
548
953
|
// are compared against the post-truncate state (undefined) rather than pre-truncate state
|
|
@@ -562,30 +967,28 @@ export class CollectionStateManager<
|
|
|
562
967
|
const key = operation.key as TKey
|
|
563
968
|
this.syncedKeys.add(key)
|
|
564
969
|
|
|
565
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
{},
|
|
575
|
-
this.syncedMetadata.get(key),
|
|
576
|
-
operation.metadata,
|
|
577
|
-
),
|
|
578
|
-
)
|
|
579
|
-
break
|
|
580
|
-
case `delete`:
|
|
581
|
-
this.syncedMetadata.delete(key)
|
|
582
|
-
break
|
|
583
|
-
}
|
|
970
|
+
// Determine origin: 'local' for local-only collections or pending local changes
|
|
971
|
+
const origin: VirtualOrigin =
|
|
972
|
+
this.isLocalOnly ||
|
|
973
|
+
this.pendingLocalChanges.has(key) ||
|
|
974
|
+
this.pendingLocalOrigins.has(key) ||
|
|
975
|
+
truncatePendingLocalChanges?.has(key) === true ||
|
|
976
|
+
truncatePendingLocalOrigins?.has(key) === true
|
|
977
|
+
? 'local'
|
|
978
|
+
: 'remote'
|
|
584
979
|
|
|
585
980
|
// Update synced data
|
|
586
981
|
switch (operation.type) {
|
|
587
982
|
case `insert`:
|
|
588
983
|
this.syncedData.set(key, operation.value)
|
|
984
|
+
this.rowOrigins.set(key, origin)
|
|
985
|
+
// Clear pending local changes now that sync has confirmed
|
|
986
|
+
this.pendingLocalChanges.delete(key)
|
|
987
|
+
this.pendingLocalOrigins.delete(key)
|
|
988
|
+
this.pendingOptimisticUpserts.delete(key)
|
|
989
|
+
this.pendingOptimisticDeletes.delete(key)
|
|
990
|
+
this.pendingOptimisticDirectUpserts.delete(key)
|
|
991
|
+
this.pendingOptimisticDirectDeletes.delete(key)
|
|
589
992
|
break
|
|
590
993
|
case `update`: {
|
|
591
994
|
if (rowUpdateMode === `partial`) {
|
|
@@ -598,13 +1001,49 @@ export class CollectionStateManager<
|
|
|
598
1001
|
} else {
|
|
599
1002
|
this.syncedData.set(key, operation.value)
|
|
600
1003
|
}
|
|
1004
|
+
this.rowOrigins.set(key, origin)
|
|
1005
|
+
// Clear pending local changes now that sync has confirmed
|
|
1006
|
+
this.pendingLocalChanges.delete(key)
|
|
1007
|
+
this.pendingLocalOrigins.delete(key)
|
|
1008
|
+
this.pendingOptimisticUpserts.delete(key)
|
|
1009
|
+
this.pendingOptimisticDeletes.delete(key)
|
|
1010
|
+
this.pendingOptimisticDirectUpserts.delete(key)
|
|
1011
|
+
this.pendingOptimisticDirectDeletes.delete(key)
|
|
601
1012
|
break
|
|
602
1013
|
}
|
|
603
1014
|
case `delete`:
|
|
604
1015
|
this.syncedData.delete(key)
|
|
1016
|
+
this.syncedMetadata.delete(key)
|
|
1017
|
+
// Clean up origin and pending tracking for deleted rows
|
|
1018
|
+
this.rowOrigins.delete(key)
|
|
1019
|
+
this.pendingLocalChanges.delete(key)
|
|
1020
|
+
this.pendingLocalOrigins.delete(key)
|
|
1021
|
+
this.pendingOptimisticUpserts.delete(key)
|
|
1022
|
+
this.pendingOptimisticDeletes.delete(key)
|
|
1023
|
+
this.pendingOptimisticDirectUpserts.delete(key)
|
|
1024
|
+
this.pendingOptimisticDirectDeletes.delete(key)
|
|
605
1025
|
break
|
|
606
1026
|
}
|
|
607
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
for (const [key, metadataWrite] of transaction.rowMetadataWrites) {
|
|
1030
|
+
if (metadataWrite.type === `delete`) {
|
|
1031
|
+
this.syncedMetadata.delete(key)
|
|
1032
|
+
continue
|
|
1033
|
+
}
|
|
1034
|
+
this.syncedMetadata.set(key, metadataWrite.value)
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
for (const [
|
|
1038
|
+
key,
|
|
1039
|
+
metadataWrite,
|
|
1040
|
+
] of transaction.collectionMetadataWrites) {
|
|
1041
|
+
if (metadataWrite.type === `delete`) {
|
|
1042
|
+
this.syncedCollectionMetadata.delete(key)
|
|
1043
|
+
continue
|
|
1044
|
+
}
|
|
1045
|
+
this.syncedCollectionMetadata.set(key, metadataWrite.value)
|
|
1046
|
+
}
|
|
608
1047
|
}
|
|
609
1048
|
|
|
610
1049
|
// After applying synced operations, if this commit included a truncate,
|
|
@@ -721,30 +1160,30 @@ export class CollectionStateManager<
|
|
|
721
1160
|
}
|
|
722
1161
|
}
|
|
723
1162
|
|
|
724
|
-
// Check for redundant sync operations that match completed optimistic operations
|
|
725
|
-
const completedOptimisticOps = new Map<TKey, any>()
|
|
726
|
-
|
|
727
|
-
for (const transaction of this.transactions.values()) {
|
|
728
|
-
if (transaction.state === `completed`) {
|
|
729
|
-
for (const mutation of transaction.mutations) {
|
|
730
|
-
if (
|
|
731
|
-
mutation.optimistic &&
|
|
732
|
-
this.isThisCollection(mutation.collection) &&
|
|
733
|
-
changedKeys.has(mutation.key)
|
|
734
|
-
) {
|
|
735
|
-
completedOptimisticOps.set(mutation.key, {
|
|
736
|
-
type: mutation.type,
|
|
737
|
-
value: mutation.modified,
|
|
738
|
-
})
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
1163
|
// Now check what actually changed in the final visible state
|
|
745
1164
|
for (const key of changedKeys) {
|
|
746
1165
|
const previousVisibleValue = currentVisibleState.get(key)
|
|
747
1166
|
const newVisibleValue = this.get(key) // This returns the new derived state
|
|
1167
|
+
const previousVirtualProps = this.getVirtualPropsSnapshotForState(key, {
|
|
1168
|
+
rowOrigins: previousRowOrigins,
|
|
1169
|
+
optimisticUpserts: previousOptimisticUpserts,
|
|
1170
|
+
optimisticDeletes: previousOptimisticDeletes,
|
|
1171
|
+
completedOptimisticKeys: completedOptimisticOps,
|
|
1172
|
+
})
|
|
1173
|
+
const nextVirtualProps = this.getVirtualPropsSnapshotForState(key)
|
|
1174
|
+
const virtualChanged =
|
|
1175
|
+
previousVirtualProps.$synced !== nextVirtualProps.$synced ||
|
|
1176
|
+
previousVirtualProps.$origin !== nextVirtualProps.$origin
|
|
1177
|
+
const previousValueWithVirtual =
|
|
1178
|
+
previousVisibleValue !== undefined
|
|
1179
|
+
? enrichRowWithVirtualProps(
|
|
1180
|
+
previousVisibleValue,
|
|
1181
|
+
key,
|
|
1182
|
+
this.collection.id,
|
|
1183
|
+
() => previousVirtualProps.$synced,
|
|
1184
|
+
() => previousVirtualProps.$origin,
|
|
1185
|
+
)
|
|
1186
|
+
: undefined
|
|
748
1187
|
|
|
749
1188
|
// Check if this sync operation is redundant with a completed optimistic operation
|
|
750
1189
|
const completedOp = completedOptimisticOps.get(key)
|
|
@@ -766,37 +1205,65 @@ export class CollectionStateManager<
|
|
|
766
1205
|
}
|
|
767
1206
|
}
|
|
768
1207
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
)
|
|
1208
|
+
const shouldEmitVirtualUpdate =
|
|
1209
|
+
virtualChanged &&
|
|
1210
|
+
previousVisibleValue !== undefined &&
|
|
1211
|
+
newVisibleValue !== undefined &&
|
|
1212
|
+
deepEquals(previousVisibleValue, newVisibleValue)
|
|
1213
|
+
|
|
1214
|
+
if (isRedundantSync && !shouldEmitVirtualUpdate) {
|
|
1215
|
+
continue
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if (
|
|
1219
|
+
previousVisibleValue === undefined &&
|
|
1220
|
+
newVisibleValue !== undefined
|
|
1221
|
+
) {
|
|
1222
|
+
const completedOptimisticOp = completedOptimisticOps.get(key)
|
|
1223
|
+
if (completedOptimisticOp) {
|
|
1224
|
+
const previousValueFromCompleted = completedOptimisticOp.value
|
|
1225
|
+
const previousValueWithVirtualFromCompleted =
|
|
1226
|
+
enrichRowWithVirtualProps(
|
|
1227
|
+
previousValueFromCompleted,
|
|
1228
|
+
key,
|
|
1229
|
+
this.collection.id,
|
|
1230
|
+
() => previousVirtualProps.$synced,
|
|
1231
|
+
() => previousVirtualProps.$origin,
|
|
1232
|
+
)
|
|
774
1233
|
events.push({
|
|
775
|
-
type: `
|
|
1234
|
+
type: `update`,
|
|
776
1235
|
key,
|
|
777
1236
|
value: newVisibleValue,
|
|
1237
|
+
previousValue: previousValueWithVirtualFromCompleted,
|
|
778
1238
|
})
|
|
779
|
-
} else
|
|
780
|
-
previousVisibleValue !== undefined &&
|
|
781
|
-
newVisibleValue === undefined
|
|
782
|
-
) {
|
|
783
|
-
events.push({
|
|
784
|
-
type: `delete`,
|
|
785
|
-
key,
|
|
786
|
-
value: previousVisibleValue,
|
|
787
|
-
})
|
|
788
|
-
} else if (
|
|
789
|
-
previousVisibleValue !== undefined &&
|
|
790
|
-
newVisibleValue !== undefined &&
|
|
791
|
-
!deepEquals(previousVisibleValue, newVisibleValue)
|
|
792
|
-
) {
|
|
1239
|
+
} else {
|
|
793
1240
|
events.push({
|
|
794
|
-
type: `
|
|
1241
|
+
type: `insert`,
|
|
795
1242
|
key,
|
|
796
1243
|
value: newVisibleValue,
|
|
797
|
-
previousValue: previousVisibleValue,
|
|
798
1244
|
})
|
|
799
1245
|
}
|
|
1246
|
+
} else if (
|
|
1247
|
+
previousVisibleValue !== undefined &&
|
|
1248
|
+
newVisibleValue === undefined
|
|
1249
|
+
) {
|
|
1250
|
+
events.push({
|
|
1251
|
+
type: `delete`,
|
|
1252
|
+
key,
|
|
1253
|
+
value: previousValueWithVirtual ?? previousVisibleValue,
|
|
1254
|
+
})
|
|
1255
|
+
} else if (
|
|
1256
|
+
previousVisibleValue !== undefined &&
|
|
1257
|
+
newVisibleValue !== undefined &&
|
|
1258
|
+
(!deepEquals(previousVisibleValue, newVisibleValue) ||
|
|
1259
|
+
shouldEmitVirtualUpdate)
|
|
1260
|
+
) {
|
|
1261
|
+
events.push({
|
|
1262
|
+
type: `update`,
|
|
1263
|
+
key,
|
|
1264
|
+
value: newVisibleValue,
|
|
1265
|
+
previousValue: previousValueWithVirtual ?? previousVisibleValue,
|
|
1266
|
+
})
|
|
800
1267
|
}
|
|
801
1268
|
}
|
|
802
1269
|
|
|
@@ -847,7 +1314,7 @@ export class CollectionStateManager<
|
|
|
847
1314
|
.catch(() => {
|
|
848
1315
|
// Transaction failed, but we want to keep failed transactions for reference
|
|
849
1316
|
// so don't remove it.
|
|
850
|
-
//
|
|
1317
|
+
// Rollback already triggers state recomputation via touchCollection().
|
|
851
1318
|
})
|
|
852
1319
|
}
|
|
853
1320
|
|
|
@@ -906,8 +1373,15 @@ export class CollectionStateManager<
|
|
|
906
1373
|
public cleanup(): void {
|
|
907
1374
|
this.syncedData.clear()
|
|
908
1375
|
this.syncedMetadata.clear()
|
|
1376
|
+
this.syncedCollectionMetadata.clear()
|
|
909
1377
|
this.optimisticUpserts.clear()
|
|
910
1378
|
this.optimisticDeletes.clear()
|
|
1379
|
+
this.pendingOptimisticUpserts.clear()
|
|
1380
|
+
this.pendingOptimisticDeletes.clear()
|
|
1381
|
+
this.pendingOptimisticDirectUpserts.clear()
|
|
1382
|
+
this.pendingOptimisticDirectDeletes.clear()
|
|
1383
|
+
this.clearOriginTrackingState()
|
|
1384
|
+
this.isLocalOnly = false
|
|
911
1385
|
this.size = 0
|
|
912
1386
|
this.pendingSyncedTransactions = []
|
|
913
1387
|
this.syncedKeys.clear()
|