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