@tanstack/db 0.5.33 → 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.
Files changed (273) hide show
  1. package/dist/cjs/collection/change-events.cjs.map +1 -1
  2. package/dist/cjs/collection/change-events.d.cts +3 -2
  3. package/dist/cjs/collection/changes.cjs +13 -4
  4. package/dist/cjs/collection/changes.cjs.map +1 -1
  5. package/dist/cjs/collection/changes.d.cts +10 -1
  6. package/dist/cjs/collection/cleanup-queue.cjs +89 -0
  7. package/dist/cjs/collection/cleanup-queue.cjs.map +1 -0
  8. package/dist/cjs/collection/cleanup-queue.d.cts +30 -0
  9. package/dist/cjs/collection/events.cjs +14 -0
  10. package/dist/cjs/collection/events.cjs.map +1 -1
  11. package/dist/cjs/collection/events.d.cts +39 -1
  12. package/dist/cjs/collection/index.cjs +66 -28
  13. package/dist/cjs/collection/index.cjs.map +1 -1
  14. package/dist/cjs/collection/index.d.cts +49 -36
  15. package/dist/cjs/collection/indexes.cjs +211 -62
  16. package/dist/cjs/collection/indexes.cjs.map +1 -1
  17. package/dist/cjs/collection/indexes.d.cts +27 -17
  18. package/dist/cjs/collection/lifecycle.cjs +5 -22
  19. package/dist/cjs/collection/lifecycle.cjs.map +1 -1
  20. package/dist/cjs/collection/lifecycle.d.cts +0 -1
  21. package/dist/cjs/collection/mutations.cjs +18 -0
  22. package/dist/cjs/collection/mutations.cjs.map +1 -1
  23. package/dist/cjs/collection/mutations.d.cts +1 -0
  24. package/dist/cjs/collection/state.cjs +381 -53
  25. package/dist/cjs/collection/state.cjs.map +1 -1
  26. package/dist/cjs/collection/state.d.cts +65 -1
  27. package/dist/cjs/collection/subscription.cjs +6 -0
  28. package/dist/cjs/collection/subscription.cjs.map +1 -1
  29. package/dist/cjs/collection/subscription.d.cts +4 -0
  30. package/dist/cjs/collection/sync.cjs +108 -1
  31. package/dist/cjs/collection/sync.cjs.map +1 -1
  32. package/dist/cjs/collection/sync.d.cts +2 -0
  33. package/dist/cjs/collection/transaction-metadata.cjs +5 -0
  34. package/dist/cjs/collection/transaction-metadata.cjs.map +1 -0
  35. package/dist/cjs/collection/transaction-metadata.d.cts +1 -0
  36. package/dist/cjs/errors.cjs +8 -0
  37. package/dist/cjs/errors.cjs.map +1 -1
  38. package/dist/cjs/errors.d.cts +3 -0
  39. package/dist/cjs/index.cjs +22 -4
  40. package/dist/cjs/index.cjs.map +1 -1
  41. package/dist/cjs/index.d.cts +11 -3
  42. package/dist/cjs/indexes/auto-index.cjs +13 -6
  43. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  44. package/dist/cjs/indexes/base-index.cjs +0 -3
  45. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  46. package/dist/cjs/indexes/base-index.d.cts +2 -6
  47. package/dist/cjs/indexes/basic-index.cjs +361 -0
  48. package/dist/cjs/indexes/basic-index.cjs.map +1 -0
  49. package/dist/cjs/indexes/basic-index.d.cts +102 -0
  50. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  51. package/dist/cjs/indexes/btree-index.d.cts +1 -1
  52. package/dist/cjs/indexes/index-options.d.cts +8 -9
  53. package/dist/cjs/indexes/index-registry.cjs +89 -0
  54. package/dist/cjs/indexes/index-registry.cjs.map +1 -0
  55. package/dist/cjs/indexes/index-registry.d.cts +61 -0
  56. package/dist/cjs/local-only.cjs +5 -0
  57. package/dist/cjs/local-only.cjs.map +1 -1
  58. package/dist/cjs/query/builder/functions.cjs +27 -11
  59. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  60. package/dist/cjs/query/builder/functions.d.cts +25 -3
  61. package/dist/cjs/query/builder/index.cjs +200 -39
  62. package/dist/cjs/query/builder/index.cjs.map +1 -1
  63. package/dist/cjs/query/builder/index.d.cts +4 -3
  64. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  65. package/dist/cjs/query/builder/ref-proxy.d.cts +14 -3
  66. package/dist/cjs/query/builder/types.d.cts +84 -19
  67. package/dist/cjs/query/compiler/evaluators.cjs +51 -0
  68. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  69. package/dist/cjs/query/compiler/group-by.cjs +100 -28
  70. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  71. package/dist/cjs/query/compiler/group-by.d.cts +4 -2
  72. package/dist/cjs/query/compiler/index.cjs +283 -11
  73. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  74. package/dist/cjs/query/compiler/index.d.cts +30 -2
  75. package/dist/cjs/query/compiler/order-by.cjs +29 -10
  76. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  77. package/dist/cjs/query/compiler/order-by.d.cts +1 -1
  78. package/dist/cjs/query/compiler/select.cjs +8 -0
  79. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  80. package/dist/cjs/query/index.d.cts +2 -1
  81. package/dist/cjs/query/ir.cjs +18 -1
  82. package/dist/cjs/query/ir.cjs.map +1 -1
  83. package/dist/cjs/query/ir.d.cts +21 -1
  84. package/dist/cjs/query/live/collection-config-builder.cjs +501 -5
  85. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  86. package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
  87. package/dist/cjs/query/live/types.d.cts +3 -3
  88. package/dist/cjs/query/live/utils.cjs +43 -3
  89. package/dist/cjs/query/live/utils.cjs.map +1 -1
  90. package/dist/cjs/query/live/utils.d.cts +1 -0
  91. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  92. package/dist/cjs/query/live-query-collection.d.cts +9 -6
  93. package/dist/cjs/query/query-once.cjs.map +1 -1
  94. package/dist/cjs/query/query-once.d.cts +7 -5
  95. package/dist/cjs/query/subset-dedupe.cjs +9 -3
  96. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  97. package/dist/cjs/types.d.cts +42 -8
  98. package/dist/cjs/utils/array-utils.cjs +27 -0
  99. package/dist/cjs/utils/array-utils.cjs.map +1 -0
  100. package/dist/cjs/utils/array-utils.d.cts +16 -0
  101. package/dist/cjs/utils/comparison.cjs +11 -0
  102. package/dist/cjs/utils/comparison.cjs.map +1 -1
  103. package/dist/cjs/utils/index-optimization.cjs +4 -0
  104. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  105. package/dist/cjs/utils.cjs +7 -9
  106. package/dist/cjs/utils.cjs.map +1 -1
  107. package/dist/cjs/utils.d.cts +6 -1
  108. package/dist/cjs/virtual-props.cjs +33 -0
  109. package/dist/cjs/virtual-props.cjs.map +1 -0
  110. package/dist/cjs/virtual-props.d.cts +196 -0
  111. package/dist/esm/collection/change-events.d.ts +3 -2
  112. package/dist/esm/collection/change-events.js.map +1 -1
  113. package/dist/esm/collection/changes.d.ts +10 -1
  114. package/dist/esm/collection/changes.js +13 -4
  115. package/dist/esm/collection/changes.js.map +1 -1
  116. package/dist/esm/collection/cleanup-queue.d.ts +30 -0
  117. package/dist/esm/collection/cleanup-queue.js +89 -0
  118. package/dist/esm/collection/cleanup-queue.js.map +1 -0
  119. package/dist/esm/collection/events.d.ts +39 -1
  120. package/dist/esm/collection/events.js +14 -0
  121. package/dist/esm/collection/events.js.map +1 -1
  122. package/dist/esm/collection/index.d.ts +49 -36
  123. package/dist/esm/collection/index.js +67 -29
  124. package/dist/esm/collection/index.js.map +1 -1
  125. package/dist/esm/collection/indexes.d.ts +27 -17
  126. package/dist/esm/collection/indexes.js +211 -62
  127. package/dist/esm/collection/indexes.js.map +1 -1
  128. package/dist/esm/collection/lifecycle.d.ts +0 -1
  129. package/dist/esm/collection/lifecycle.js +5 -22
  130. package/dist/esm/collection/lifecycle.js.map +1 -1
  131. package/dist/esm/collection/mutations.d.ts +1 -0
  132. package/dist/esm/collection/mutations.js +18 -0
  133. package/dist/esm/collection/mutations.js.map +1 -1
  134. package/dist/esm/collection/state.d.ts +65 -1
  135. package/dist/esm/collection/state.js +381 -53
  136. package/dist/esm/collection/state.js.map +1 -1
  137. package/dist/esm/collection/subscription.d.ts +4 -0
  138. package/dist/esm/collection/subscription.js +6 -0
  139. package/dist/esm/collection/subscription.js.map +1 -1
  140. package/dist/esm/collection/sync.d.ts +2 -0
  141. package/dist/esm/collection/sync.js +108 -1
  142. package/dist/esm/collection/sync.js.map +1 -1
  143. package/dist/esm/collection/transaction-metadata.d.ts +1 -0
  144. package/dist/esm/collection/transaction-metadata.js +5 -0
  145. package/dist/esm/collection/transaction-metadata.js.map +1 -0
  146. package/dist/esm/errors.d.ts +3 -0
  147. package/dist/esm/errors.js +8 -0
  148. package/dist/esm/errors.js.map +1 -1
  149. package/dist/esm/index.d.ts +11 -3
  150. package/dist/esm/index.js +25 -7
  151. package/dist/esm/index.js.map +1 -1
  152. package/dist/esm/indexes/auto-index.js +13 -6
  153. package/dist/esm/indexes/auto-index.js.map +1 -1
  154. package/dist/esm/indexes/base-index.d.ts +2 -6
  155. package/dist/esm/indexes/base-index.js +1 -4
  156. package/dist/esm/indexes/base-index.js.map +1 -1
  157. package/dist/esm/indexes/basic-index.d.ts +102 -0
  158. package/dist/esm/indexes/basic-index.js +361 -0
  159. package/dist/esm/indexes/basic-index.js.map +1 -0
  160. package/dist/esm/indexes/btree-index.d.ts +1 -1
  161. package/dist/esm/indexes/btree-index.js.map +1 -1
  162. package/dist/esm/indexes/index-options.d.ts +8 -9
  163. package/dist/esm/indexes/index-registry.d.ts +61 -0
  164. package/dist/esm/indexes/index-registry.js +89 -0
  165. package/dist/esm/indexes/index-registry.js.map +1 -0
  166. package/dist/esm/local-only.js +5 -0
  167. package/dist/esm/local-only.js.map +1 -1
  168. package/dist/esm/query/builder/functions.d.ts +25 -3
  169. package/dist/esm/query/builder/functions.js +27 -11
  170. package/dist/esm/query/builder/functions.js.map +1 -1
  171. package/dist/esm/query/builder/index.d.ts +4 -3
  172. package/dist/esm/query/builder/index.js +201 -40
  173. package/dist/esm/query/builder/index.js.map +1 -1
  174. package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
  175. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  176. package/dist/esm/query/builder/types.d.ts +84 -19
  177. package/dist/esm/query/compiler/evaluators.js +51 -0
  178. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  179. package/dist/esm/query/compiler/group-by.d.ts +4 -2
  180. package/dist/esm/query/compiler/group-by.js +101 -29
  181. package/dist/esm/query/compiler/group-by.js.map +1 -1
  182. package/dist/esm/query/compiler/index.d.ts +30 -2
  183. package/dist/esm/query/compiler/index.js +285 -13
  184. package/dist/esm/query/compiler/index.js.map +1 -1
  185. package/dist/esm/query/compiler/order-by.d.ts +1 -1
  186. package/dist/esm/query/compiler/order-by.js +30 -11
  187. package/dist/esm/query/compiler/order-by.js.map +1 -1
  188. package/dist/esm/query/compiler/select.js +8 -0
  189. package/dist/esm/query/compiler/select.js.map +1 -1
  190. package/dist/esm/query/index.d.ts +2 -1
  191. package/dist/esm/query/ir.d.ts +21 -1
  192. package/dist/esm/query/ir.js +18 -1
  193. package/dist/esm/query/ir.js.map +1 -1
  194. package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
  195. package/dist/esm/query/live/collection-config-builder.js +503 -7
  196. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  197. package/dist/esm/query/live/types.d.ts +3 -3
  198. package/dist/esm/query/live/utils.d.ts +1 -0
  199. package/dist/esm/query/live/utils.js +43 -3
  200. package/dist/esm/query/live/utils.js.map +1 -1
  201. package/dist/esm/query/live-query-collection.d.ts +9 -6
  202. package/dist/esm/query/live-query-collection.js.map +1 -1
  203. package/dist/esm/query/query-once.d.ts +7 -5
  204. package/dist/esm/query/query-once.js.map +1 -1
  205. package/dist/esm/query/subset-dedupe.js +9 -3
  206. package/dist/esm/query/subset-dedupe.js.map +1 -1
  207. package/dist/esm/types.d.ts +42 -8
  208. package/dist/esm/utils/array-utils.d.ts +16 -0
  209. package/dist/esm/utils/array-utils.js +27 -0
  210. package/dist/esm/utils/array-utils.js.map +1 -0
  211. package/dist/esm/utils/comparison.js +11 -0
  212. package/dist/esm/utils/comparison.js.map +1 -1
  213. package/dist/esm/utils/index-optimization.js +4 -0
  214. package/dist/esm/utils/index-optimization.js.map +1 -1
  215. package/dist/esm/utils.d.ts +6 -1
  216. package/dist/esm/utils.js +7 -9
  217. package/dist/esm/utils.js.map +1 -1
  218. package/dist/esm/virtual-props.d.ts +196 -0
  219. package/dist/esm/virtual-props.js +33 -0
  220. package/dist/esm/virtual-props.js.map +1 -0
  221. package/package.json +2 -2
  222. package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
  223. package/src/collection/change-events.ts +13 -9
  224. package/src/collection/changes.ts +30 -7
  225. package/src/collection/cleanup-queue.ts +105 -0
  226. package/src/collection/events.ts +65 -0
  227. package/src/collection/index.ts +110 -45
  228. package/src/collection/indexes.ts +283 -76
  229. package/src/collection/lifecycle.ts +5 -26
  230. package/src/collection/mutations.ts +21 -0
  231. package/src/collection/state.ts +545 -71
  232. package/src/collection/subscription.ts +7 -0
  233. package/src/collection/sync.ts +137 -0
  234. package/src/collection/transaction-metadata.ts +1 -0
  235. package/src/errors.ts +9 -0
  236. package/src/index.ts +46 -3
  237. package/src/indexes/auto-index.ts +18 -8
  238. package/src/indexes/base-index.ts +2 -10
  239. package/src/indexes/basic-index.ts +507 -0
  240. package/src/indexes/btree-index.ts +1 -1
  241. package/src/indexes/index-options.ts +17 -37
  242. package/src/indexes/index-registry.ts +174 -0
  243. package/src/local-only.ts +7 -0
  244. package/src/query/builder/functions.ts +84 -7
  245. package/src/query/builder/index.ts +329 -9
  246. package/src/query/builder/ref-proxy.ts +22 -4
  247. package/src/query/builder/types.ts +257 -62
  248. package/src/query/compiler/evaluators.ts +57 -0
  249. package/src/query/compiler/group-by.ts +156 -35
  250. package/src/query/compiler/index.ts +445 -15
  251. package/src/query/compiler/order-by.ts +51 -12
  252. package/src/query/compiler/select.ts +9 -0
  253. package/src/query/index.ts +7 -0
  254. package/src/query/ir.ts +23 -2
  255. package/src/query/live/collection-config-builder.ts +809 -9
  256. package/src/query/live/types.ts +10 -4
  257. package/src/query/live/utils.ts +64 -3
  258. package/src/query/live-query-collection.ts +43 -18
  259. package/src/query/query-once.ts +31 -12
  260. package/src/query/subset-dedupe.ts +11 -7
  261. package/src/types.ts +49 -9
  262. package/src/utils/array-utils.ts +49 -0
  263. package/src/utils/comparison.ts +14 -0
  264. package/src/utils/index-optimization.ts +4 -0
  265. package/src/utils.ts +12 -9
  266. package/src/virtual-props.ts +282 -0
  267. package/dist/cjs/indexes/lazy-index.cjs +0 -190
  268. package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
  269. package/dist/cjs/indexes/lazy-index.d.cts +0 -96
  270. package/dist/esm/indexes/lazy-index.d.ts +0 -96
  271. package/dist/esm/indexes/lazy-index.js +0 -190
  272. package/dist/esm/indexes/lazy-index.js.map +0 -1
  273. package/src/indexes/lazy-index.ts +0 -251
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const utils = require("../utils.cjs");
4
4
  const SortedMap = require("../SortedMap.cjs");
5
+ const virtualProps = require("../virtual-props.cjs");
6
+ const transactionMetadata = require("./transaction-metadata.cjs");
5
7
  class CollectionStateManager {
6
8
  /**
7
9
  * Creates a new CollectionState manager
@@ -9,14 +11,24 @@ class CollectionStateManager {
9
11
  constructor(config) {
10
12
  this.pendingSyncedTransactions = [];
11
13
  this.syncedMetadata = /* @__PURE__ */ new Map();
14
+ this.syncedCollectionMetadata = /* @__PURE__ */ new Map();
12
15
  this.optimisticUpserts = /* @__PURE__ */ new Map();
13
16
  this.optimisticDeletes = /* @__PURE__ */ new Set();
17
+ this.pendingOptimisticUpserts = /* @__PURE__ */ new Map();
18
+ this.pendingOptimisticDeletes = /* @__PURE__ */ new Set();
19
+ this.pendingOptimisticDirectUpserts = /* @__PURE__ */ new Set();
20
+ this.pendingOptimisticDirectDeletes = /* @__PURE__ */ new Set();
21
+ this.rowOrigins = /* @__PURE__ */ new Map();
22
+ this.pendingLocalChanges = /* @__PURE__ */ new Set();
23
+ this.pendingLocalOrigins = /* @__PURE__ */ new Set();
24
+ this.virtualPropsCache = /* @__PURE__ */ new WeakMap();
14
25
  this.size = 0;
15
26
  this.syncedKeys = /* @__PURE__ */ new Set();
16
27
  this.preSyncVisibleState = /* @__PURE__ */ new Map();
17
28
  this.recentlySyncedKeys = /* @__PURE__ */ new Set();
18
29
  this.hasReceivedFirstCommit = false;
19
30
  this.isCommittingSyncTransactions = false;
31
+ this.isLocalOnly = false;
20
32
  this.commitPendingTransactions = () => {
21
33
  let hasPersistingTransaction = false;
22
34
  for (const transaction of this.transactions.values()) {
@@ -54,12 +66,20 @@ class CollectionStateManager {
54
66
  );
55
67
  if (!hasPersistingTransaction || hasTruncateSync || hasImmediateSync) {
56
68
  this.isCommittingSyncTransactions = true;
69
+ const previousRowOrigins = new Map(this.rowOrigins);
70
+ const previousOptimisticUpserts = new Map(this.optimisticUpserts);
71
+ const previousOptimisticDeletes = new Set(this.optimisticDeletes);
57
72
  const truncateOptimisticSnapshot = hasTruncateSync ? committedSyncedTransactions.find((t) => t.truncate)?.optimisticSnapshot : null;
73
+ let truncatePendingLocalChanges;
74
+ let truncatePendingLocalOrigins;
58
75
  const changedKeys = /* @__PURE__ */ new Set();
59
76
  for (const transaction of committedSyncedTransactions) {
60
77
  for (const operation of transaction.operations) {
61
78
  changedKeys.add(operation.key);
62
79
  }
80
+ for (const [key] of transaction.rowMetadataWrites) {
81
+ changedKeys.add(key);
82
+ }
63
83
  }
64
84
  let currentVisibleState = this.preSyncVisibleState;
65
85
  if (currentVisibleState.size === 0) {
@@ -73,6 +93,21 @@ class CollectionStateManager {
73
93
  }
74
94
  const events = [];
75
95
  const rowUpdateMode = this.config.sync.rowUpdateMode || `partial`;
96
+ const completedOptimisticOps = /* @__PURE__ */ new Map();
97
+ for (const transaction of this.transactions.values()) {
98
+ if (transaction.state === `completed`) {
99
+ for (const mutation of transaction.mutations) {
100
+ if (this.isThisCollection(mutation.collection)) {
101
+ if (mutation.optimistic) {
102
+ completedOptimisticOps.set(mutation.key, {
103
+ type: mutation.type,
104
+ value: mutation.modified
105
+ });
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
76
111
  for (const transaction of committedSyncedTransactions) {
77
112
  if (transaction.truncate) {
78
113
  const visibleKeys = /* @__PURE__ */ new Set([
@@ -86,9 +121,12 @@ class CollectionStateManager {
86
121
  events.push({ type: `delete`, key, value: previousValue });
87
122
  }
88
123
  }
124
+ truncatePendingLocalChanges = new Set(this.pendingLocalChanges);
125
+ truncatePendingLocalOrigins = new Set(this.pendingLocalOrigins);
89
126
  this.syncedData.clear();
90
127
  this.syncedMetadata.clear();
91
128
  this.syncedKeys.clear();
129
+ this.clearOriginTrackingState();
92
130
  for (const key of changedKeys) {
93
131
  currentVisibleState.delete(key);
94
132
  }
@@ -100,27 +138,17 @@ class CollectionStateManager {
100
138
  for (const operation of transaction.operations) {
101
139
  const key = operation.key;
102
140
  this.syncedKeys.add(key);
103
- switch (operation.type) {
104
- case `insert`:
105
- this.syncedMetadata.set(key, operation.metadata);
106
- break;
107
- case `update`:
108
- this.syncedMetadata.set(
109
- key,
110
- Object.assign(
111
- {},
112
- this.syncedMetadata.get(key),
113
- operation.metadata
114
- )
115
- );
116
- break;
117
- case `delete`:
118
- this.syncedMetadata.delete(key);
119
- break;
120
- }
141
+ const origin = this.isLocalOnly || this.pendingLocalChanges.has(key) || this.pendingLocalOrigins.has(key) || truncatePendingLocalChanges?.has(key) === true || truncatePendingLocalOrigins?.has(key) === true ? "local" : "remote";
121
142
  switch (operation.type) {
122
143
  case `insert`:
123
144
  this.syncedData.set(key, operation.value);
145
+ this.rowOrigins.set(key, origin);
146
+ this.pendingLocalChanges.delete(key);
147
+ this.pendingLocalOrigins.delete(key);
148
+ this.pendingOptimisticUpserts.delete(key);
149
+ this.pendingOptimisticDeletes.delete(key);
150
+ this.pendingOptimisticDirectUpserts.delete(key);
151
+ this.pendingOptimisticDirectDeletes.delete(key);
124
152
  break;
125
153
  case `update`: {
126
154
  if (rowUpdateMode === `partial`) {
@@ -133,13 +161,45 @@ class CollectionStateManager {
133
161
  } else {
134
162
  this.syncedData.set(key, operation.value);
135
163
  }
164
+ this.rowOrigins.set(key, origin);
165
+ this.pendingLocalChanges.delete(key);
166
+ this.pendingLocalOrigins.delete(key);
167
+ this.pendingOptimisticUpserts.delete(key);
168
+ this.pendingOptimisticDeletes.delete(key);
169
+ this.pendingOptimisticDirectUpserts.delete(key);
170
+ this.pendingOptimisticDirectDeletes.delete(key);
136
171
  break;
137
172
  }
138
173
  case `delete`:
139
174
  this.syncedData.delete(key);
175
+ this.syncedMetadata.delete(key);
176
+ this.rowOrigins.delete(key);
177
+ this.pendingLocalChanges.delete(key);
178
+ this.pendingLocalOrigins.delete(key);
179
+ this.pendingOptimisticUpserts.delete(key);
180
+ this.pendingOptimisticDeletes.delete(key);
181
+ this.pendingOptimisticDirectUpserts.delete(key);
182
+ this.pendingOptimisticDirectDeletes.delete(key);
140
183
  break;
141
184
  }
142
185
  }
186
+ for (const [key, metadataWrite] of transaction.rowMetadataWrites) {
187
+ if (metadataWrite.type === `delete`) {
188
+ this.syncedMetadata.delete(key);
189
+ continue;
190
+ }
191
+ this.syncedMetadata.set(key, metadataWrite.value);
192
+ }
193
+ for (const [
194
+ key,
195
+ metadataWrite
196
+ ] of transaction.collectionMetadataWrites) {
197
+ if (metadataWrite.type === `delete`) {
198
+ this.syncedCollectionMetadata.delete(key);
199
+ continue;
200
+ }
201
+ this.syncedCollectionMetadata.set(key, metadataWrite.value);
202
+ }
143
203
  }
144
204
  if (hasTruncateSync) {
145
205
  const syncedInsertedOrUpdatedKeys = /* @__PURE__ */ new Set();
@@ -223,22 +283,24 @@ class CollectionStateManager {
223
283
  }
224
284
  }
225
285
  }
226
- const completedOptimisticOps = /* @__PURE__ */ new Map();
227
- for (const transaction of this.transactions.values()) {
228
- if (transaction.state === `completed`) {
229
- for (const mutation of transaction.mutations) {
230
- if (mutation.optimistic && this.isThisCollection(mutation.collection) && changedKeys.has(mutation.key)) {
231
- completedOptimisticOps.set(mutation.key, {
232
- type: mutation.type,
233
- value: mutation.modified
234
- });
235
- }
236
- }
237
- }
238
- }
239
286
  for (const key of changedKeys) {
240
287
  const previousVisibleValue = currentVisibleState.get(key);
241
288
  const newVisibleValue = this.get(key);
289
+ const previousVirtualProps = this.getVirtualPropsSnapshotForState(key, {
290
+ rowOrigins: previousRowOrigins,
291
+ optimisticUpserts: previousOptimisticUpserts,
292
+ optimisticDeletes: previousOptimisticDeletes,
293
+ completedOptimisticKeys: completedOptimisticOps
294
+ });
295
+ const nextVirtualProps = this.getVirtualPropsSnapshotForState(key);
296
+ const virtualChanged = previousVirtualProps.$synced !== nextVirtualProps.$synced || previousVirtualProps.$origin !== nextVirtualProps.$origin;
297
+ const previousValueWithVirtual = previousVisibleValue !== void 0 ? virtualProps.enrichRowWithVirtualProps(
298
+ previousVisibleValue,
299
+ key,
300
+ this.collection.id,
301
+ () => previousVirtualProps.$synced,
302
+ () => previousVirtualProps.$origin
303
+ ) : void 0;
242
304
  const completedOp = completedOptimisticOps.get(key);
243
305
  let isRedundantSync = false;
244
306
  if (completedOp) {
@@ -248,27 +310,47 @@ class CollectionStateManager {
248
310
  isRedundantSync = true;
249
311
  }
250
312
  }
251
- if (!isRedundantSync) {
252
- if (previousVisibleValue === void 0 && newVisibleValue !== void 0) {
253
- events.push({
254
- type: `insert`,
313
+ const shouldEmitVirtualUpdate = virtualChanged && previousVisibleValue !== void 0 && newVisibleValue !== void 0 && utils.deepEquals(previousVisibleValue, newVisibleValue);
314
+ if (isRedundantSync && !shouldEmitVirtualUpdate) {
315
+ continue;
316
+ }
317
+ if (previousVisibleValue === void 0 && newVisibleValue !== void 0) {
318
+ const completedOptimisticOp = completedOptimisticOps.get(key);
319
+ if (completedOptimisticOp) {
320
+ const previousValueFromCompleted = completedOptimisticOp.value;
321
+ const previousValueWithVirtualFromCompleted = virtualProps.enrichRowWithVirtualProps(
322
+ previousValueFromCompleted,
255
323
  key,
256
- value: newVisibleValue
257
- });
258
- } else if (previousVisibleValue !== void 0 && newVisibleValue === void 0) {
324
+ this.collection.id,
325
+ () => previousVirtualProps.$synced,
326
+ () => previousVirtualProps.$origin
327
+ );
259
328
  events.push({
260
- type: `delete`,
329
+ type: `update`,
261
330
  key,
262
- value: previousVisibleValue
331
+ value: newVisibleValue,
332
+ previousValue: previousValueWithVirtualFromCompleted
263
333
  });
264
- } else if (previousVisibleValue !== void 0 && newVisibleValue !== void 0 && !utils.deepEquals(previousVisibleValue, newVisibleValue)) {
334
+ } else {
265
335
  events.push({
266
- type: `update`,
336
+ type: `insert`,
267
337
  key,
268
- value: newVisibleValue,
269
- previousValue: previousVisibleValue
338
+ value: newVisibleValue
270
339
  });
271
340
  }
341
+ } else if (previousVisibleValue !== void 0 && newVisibleValue === void 0) {
342
+ events.push({
343
+ type: `delete`,
344
+ key,
345
+ value: previousValueWithVirtual ?? previousVisibleValue
346
+ });
347
+ } else if (previousVisibleValue !== void 0 && newVisibleValue !== void 0 && (!utils.deepEquals(previousVisibleValue, newVisibleValue) || shouldEmitVirtualUpdate)) {
348
+ events.push({
349
+ type: `update`,
350
+ key,
351
+ value: newVisibleValue,
352
+ previousValue: previousValueWithVirtual ?? previousVisibleValue
353
+ });
272
354
  }
273
355
  }
274
356
  this.size = this.calculateSize();
@@ -299,6 +381,125 @@ class CollectionStateManager {
299
381
  this.indexes = deps.indexes;
300
382
  this._events = deps.events;
301
383
  }
384
+ /**
385
+ * Checks if a row has pending optimistic mutations (not yet confirmed by sync).
386
+ * Used to compute the $synced virtual property.
387
+ */
388
+ isRowSynced(key) {
389
+ if (this.isLocalOnly) {
390
+ return true;
391
+ }
392
+ return !this.optimisticUpserts.has(key) && !this.optimisticDeletes.has(key);
393
+ }
394
+ /**
395
+ * Gets the origin of the last confirmed change to a row.
396
+ * Returns 'local' if the row has optimistic mutations (optimistic changes are local).
397
+ * Used to compute the $origin virtual property.
398
+ */
399
+ getRowOrigin(key) {
400
+ if (this.isLocalOnly) {
401
+ return "local";
402
+ }
403
+ if (this.optimisticUpserts.has(key) || this.optimisticDeletes.has(key)) {
404
+ return "local";
405
+ }
406
+ return this.rowOrigins.get(key) ?? "remote";
407
+ }
408
+ createVirtualPropsSnapshot(key, overrides) {
409
+ return {
410
+ $synced: overrides?.$synced ?? this.isRowSynced(key),
411
+ $origin: overrides?.$origin ?? this.getRowOrigin(key),
412
+ $key: overrides?.$key ?? key,
413
+ $collectionId: overrides?.$collectionId ?? this.collection.id
414
+ };
415
+ }
416
+ getVirtualPropsSnapshotForState(key, options) {
417
+ if (this.isLocalOnly) {
418
+ return this.createVirtualPropsSnapshot(key, {
419
+ $synced: true,
420
+ $origin: "local"
421
+ });
422
+ }
423
+ const optimisticUpserts = options?.optimisticUpserts ?? this.optimisticUpserts;
424
+ const optimisticDeletes = options?.optimisticDeletes ?? this.optimisticDeletes;
425
+ const hasOptimisticChange = optimisticUpserts.has(key) || optimisticDeletes.has(key) || options?.completedOptimisticKeys?.has(key) === true;
426
+ return this.createVirtualPropsSnapshot(key, {
427
+ $synced: !hasOptimisticChange,
428
+ $origin: hasOptimisticChange ? "local" : (options?.rowOrigins ?? this.rowOrigins).get(key) ?? "remote"
429
+ });
430
+ }
431
+ enrichWithVirtualPropsSnapshot(row, virtualProps2) {
432
+ const existingRow = row;
433
+ const synced = existingRow.$synced ?? virtualProps2.$synced;
434
+ const origin = existingRow.$origin ?? virtualProps2.$origin;
435
+ const resolvedKey = existingRow.$key ?? virtualProps2.$key;
436
+ const collectionId = existingRow.$collectionId ?? virtualProps2.$collectionId;
437
+ const cached = this.virtualPropsCache.get(row);
438
+ if (cached && cached.synced === synced && cached.origin === origin && cached.key === resolvedKey && cached.collectionId === collectionId) {
439
+ return cached.enriched;
440
+ }
441
+ const enriched = {
442
+ ...row,
443
+ $synced: synced,
444
+ $origin: origin,
445
+ $key: resolvedKey,
446
+ $collectionId: collectionId
447
+ };
448
+ this.virtualPropsCache.set(row, {
449
+ synced,
450
+ origin,
451
+ key: resolvedKey,
452
+ collectionId,
453
+ enriched
454
+ });
455
+ return enriched;
456
+ }
457
+ clearOriginTrackingState() {
458
+ this.rowOrigins.clear();
459
+ this.pendingLocalChanges.clear();
460
+ this.pendingLocalOrigins.clear();
461
+ }
462
+ /**
463
+ * Enriches a row with virtual properties using the "add-if-missing" pattern.
464
+ * If the row already has virtual properties (from an upstream collection),
465
+ * they are preserved. Otherwise, new values are computed.
466
+ */
467
+ enrichWithVirtualProps(row, key) {
468
+ return this.enrichWithVirtualPropsSnapshot(
469
+ row,
470
+ this.createVirtualPropsSnapshot(key)
471
+ );
472
+ }
473
+ /**
474
+ * Creates a change message with virtual properties.
475
+ * Uses the "add-if-missing" pattern so that pass-through from upstream
476
+ * collections works correctly.
477
+ */
478
+ enrichChangeMessage(change) {
479
+ const { __virtualProps } = change;
480
+ const enrichedValue = __virtualProps?.value ? this.enrichWithVirtualPropsSnapshot(change.value, __virtualProps.value) : this.enrichWithVirtualProps(change.value, change.key);
481
+ const enrichedPreviousValue = change.previousValue ? __virtualProps?.previousValue ? this.enrichWithVirtualPropsSnapshot(
482
+ change.previousValue,
483
+ __virtualProps.previousValue
484
+ ) : this.enrichWithVirtualProps(change.previousValue, change.key) : void 0;
485
+ return {
486
+ key: change.key,
487
+ type: change.type,
488
+ value: enrichedValue,
489
+ previousValue: enrichedPreviousValue,
490
+ metadata: change.metadata
491
+ };
492
+ }
493
+ /**
494
+ * Get the current value for a key enriched with virtual properties.
495
+ */
496
+ getWithVirtualProps(key) {
497
+ const value = this.get(key);
498
+ if (value === void 0) {
499
+ return void 0;
500
+ }
501
+ return this.enrichWithVirtualProps(value, key);
502
+ }
302
503
  /**
303
504
  * Get the current value for a key (virtual derived state)
304
505
  */
@@ -408,8 +609,95 @@ class CollectionStateManager {
408
609
  }
409
610
  const previousState = new Map(this.optimisticUpserts);
410
611
  const previousDeletes = new Set(this.optimisticDeletes);
612
+ const previousRowOrigins = new Map(this.rowOrigins);
613
+ for (const transaction of this.transactions.values()) {
614
+ const isDirectTransaction = transaction.metadata[transactionMetadata.DIRECT_TRANSACTION_METADATA_KEY] === true;
615
+ if (transaction.state === `completed`) {
616
+ for (const mutation of transaction.mutations) {
617
+ if (!this.isThisCollection(mutation.collection)) {
618
+ continue;
619
+ }
620
+ this.pendingLocalOrigins.add(mutation.key);
621
+ if (!mutation.optimistic) {
622
+ continue;
623
+ }
624
+ switch (mutation.type) {
625
+ case `insert`:
626
+ case `update`:
627
+ this.pendingOptimisticUpserts.set(
628
+ mutation.key,
629
+ mutation.modified
630
+ );
631
+ this.pendingOptimisticDeletes.delete(mutation.key);
632
+ if (isDirectTransaction) {
633
+ this.pendingOptimisticDirectUpserts.add(mutation.key);
634
+ this.pendingOptimisticDirectDeletes.delete(mutation.key);
635
+ } else {
636
+ this.pendingOptimisticDirectUpserts.delete(mutation.key);
637
+ this.pendingOptimisticDirectDeletes.delete(mutation.key);
638
+ }
639
+ break;
640
+ case `delete`:
641
+ this.pendingOptimisticUpserts.delete(mutation.key);
642
+ this.pendingOptimisticDeletes.add(mutation.key);
643
+ if (isDirectTransaction) {
644
+ this.pendingOptimisticDirectUpserts.delete(mutation.key);
645
+ this.pendingOptimisticDirectDeletes.add(mutation.key);
646
+ } else {
647
+ this.pendingOptimisticDirectUpserts.delete(mutation.key);
648
+ this.pendingOptimisticDirectDeletes.delete(mutation.key);
649
+ }
650
+ break;
651
+ }
652
+ }
653
+ } else if (transaction.state === `failed`) {
654
+ for (const mutation of transaction.mutations) {
655
+ if (!this.isThisCollection(mutation.collection)) {
656
+ continue;
657
+ }
658
+ this.pendingLocalOrigins.delete(mutation.key);
659
+ if (mutation.optimistic) {
660
+ this.pendingOptimisticUpserts.delete(mutation.key);
661
+ this.pendingOptimisticDeletes.delete(mutation.key);
662
+ this.pendingOptimisticDirectUpserts.delete(mutation.key);
663
+ this.pendingOptimisticDirectDeletes.delete(mutation.key);
664
+ }
665
+ }
666
+ }
667
+ }
411
668
  this.optimisticUpserts.clear();
412
669
  this.optimisticDeletes.clear();
670
+ this.pendingLocalChanges.clear();
671
+ const pendingSyncKeys = /* @__PURE__ */ new Set();
672
+ for (const transaction of this.pendingSyncedTransactions) {
673
+ for (const operation of transaction.operations) {
674
+ pendingSyncKeys.add(operation.key);
675
+ }
676
+ }
677
+ const staleOptimisticUpserts = [];
678
+ for (const [key, value] of this.pendingOptimisticUpserts) {
679
+ if (pendingSyncKeys.has(key) || this.pendingOptimisticDirectUpserts.has(key)) {
680
+ this.optimisticUpserts.set(key, value);
681
+ } else {
682
+ staleOptimisticUpserts.push(key);
683
+ }
684
+ }
685
+ for (const key of staleOptimisticUpserts) {
686
+ this.pendingOptimisticUpserts.delete(key);
687
+ this.pendingLocalOrigins.delete(key);
688
+ }
689
+ const staleOptimisticDeletes = [];
690
+ for (const key of this.pendingOptimisticDeletes) {
691
+ if (pendingSyncKeys.has(key) || this.pendingOptimisticDirectDeletes.has(key)) {
692
+ this.optimisticDeletes.add(key);
693
+ } else {
694
+ staleOptimisticDeletes.push(key);
695
+ }
696
+ }
697
+ for (const key of staleOptimisticDeletes) {
698
+ this.pendingOptimisticDeletes.delete(key);
699
+ this.pendingLocalOrigins.delete(key);
700
+ }
413
701
  const activeTransactions = [];
414
702
  for (const transaction of this.transactions.values()) {
415
703
  if (![`completed`, `failed`].includes(transaction.state)) {
@@ -418,7 +706,11 @@ class CollectionStateManager {
418
706
  }
419
707
  for (const transaction of activeTransactions) {
420
708
  for (const mutation of transaction.mutations) {
421
- if (this.isThisCollection(mutation.collection) && mutation.optimistic) {
709
+ if (!this.isThisCollection(mutation.collection)) {
710
+ continue;
711
+ }
712
+ this.pendingLocalChanges.add(mutation.key);
713
+ if (mutation.optimistic) {
422
714
  switch (mutation.type) {
423
715
  case `insert`:
424
716
  case `update`:
@@ -438,7 +730,12 @@ class CollectionStateManager {
438
730
  }
439
731
  this.size = this.calculateSize();
440
732
  const events = [];
441
- this.collectOptimisticChanges(previousState, previousDeletes, events);
733
+ this.collectOptimisticChanges(
734
+ previousState,
735
+ previousDeletes,
736
+ previousRowOrigins,
737
+ events
738
+ );
442
739
  const filteredEventsBySyncStatus = events.filter((event) => {
443
740
  if (!this.recentlySyncedKeys.has(event.key)) {
444
741
  return true;
@@ -449,14 +746,14 @@ class CollectionStateManager {
449
746
  return false;
450
747
  });
451
748
  if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {
452
- const pendingSyncKeys = /* @__PURE__ */ new Set();
749
+ const pendingSyncKeysForFilter = /* @__PURE__ */ new Set();
453
750
  for (const transaction of this.pendingSyncedTransactions) {
454
751
  for (const operation of transaction.operations) {
455
- pendingSyncKeys.add(operation.key);
752
+ pendingSyncKeysForFilter.add(operation.key);
456
753
  }
457
754
  }
458
755
  const filteredEvents = filteredEventsBySyncStatus.filter((event) => {
459
- if (event.type === `delete` && pendingSyncKeys.has(event.key)) {
756
+ if (event.type === `delete` && pendingSyncKeysForFilter.has(event.key)) {
460
757
  const hasActiveOptimisticMutation = activeTransactions.some(
461
758
  (tx) => tx.mutations.some(
462
759
  (m) => this.isThisCollection(m.collection) && m.key === event.key
@@ -495,7 +792,7 @@ class CollectionStateManager {
495
792
  /**
496
793
  * Collect events for optimistic changes
497
794
  */
498
- collectOptimisticChanges(previousUpserts, previousDeletes, events) {
795
+ collectOptimisticChanges(previousUpserts, previousDeletes, previousRowOrigins, events) {
499
796
  const allKeys = /* @__PURE__ */ new Set([
500
797
  ...previousUpserts.keys(),
501
798
  ...this.optimisticUpserts.keys(),
@@ -509,16 +806,40 @@ class CollectionStateManager {
509
806
  previousUpserts,
510
807
  previousDeletes
511
808
  );
809
+ const previousVirtualProps = this.getVirtualPropsSnapshotForState(key, {
810
+ rowOrigins: previousRowOrigins,
811
+ optimisticUpserts: previousUpserts,
812
+ optimisticDeletes: previousDeletes
813
+ });
814
+ const nextVirtualProps = this.getVirtualPropsSnapshotForState(key);
512
815
  if (previousValue !== void 0 && currentValue === void 0) {
513
- events.push({ type: `delete`, key, value: previousValue });
816
+ events.push({
817
+ type: `delete`,
818
+ key,
819
+ value: previousValue,
820
+ __virtualProps: {
821
+ value: previousVirtualProps
822
+ }
823
+ });
514
824
  } else if (previousValue === void 0 && currentValue !== void 0) {
515
- events.push({ type: `insert`, key, value: currentValue });
825
+ events.push({
826
+ type: `insert`,
827
+ key,
828
+ value: currentValue,
829
+ __virtualProps: {
830
+ value: nextVirtualProps
831
+ }
832
+ });
516
833
  } else if (previousValue !== void 0 && currentValue !== void 0 && previousValue !== currentValue) {
517
834
  events.push({
518
835
  type: `update`,
519
836
  key,
520
837
  value: currentValue,
521
- previousValue
838
+ previousValue,
839
+ __virtualProps: {
840
+ value: nextVirtualProps,
841
+ previousValue: previousVirtualProps
842
+ }
522
843
  });
523
844
  }
524
845
  }
@@ -588,8 +909,15 @@ class CollectionStateManager {
588
909
  cleanup() {
589
910
  this.syncedData.clear();
590
911
  this.syncedMetadata.clear();
912
+ this.syncedCollectionMetadata.clear();
591
913
  this.optimisticUpserts.clear();
592
914
  this.optimisticDeletes.clear();
915
+ this.pendingOptimisticUpserts.clear();
916
+ this.pendingOptimisticDeletes.clear();
917
+ this.pendingOptimisticDirectUpserts.clear();
918
+ this.pendingOptimisticDirectDeletes.clear();
919
+ this.clearOriginTrackingState();
920
+ this.isLocalOnly = false;
593
921
  this.size = 0;
594
922
  this.pendingSyncedTransactions = [];
595
923
  this.syncedKeys.clear();