@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
@@ -1,21 +1,173 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const lazyIndex = require("../indexes/lazy-index.cjs");
4
3
  const refProxy = require("../query/builder/ref-proxy.cjs");
5
- const btreeIndex = require("../indexes/btree-index.cjs");
4
+ const errors = require("../errors.cjs");
5
+ const INDEX_SIGNATURE_VERSION = 1;
6
+ function compareStringsCodePoint(left, right) {
7
+ if (left === right) {
8
+ return 0;
9
+ }
10
+ return left < right ? -1 : 1;
11
+ }
12
+ function resolveResolverMetadata(resolver) {
13
+ return {
14
+ kind: `constructor`,
15
+ ...resolver.name ? { name: resolver.name } : {}
16
+ };
17
+ }
18
+ function toSerializableIndexValue(value) {
19
+ if (value == null) {
20
+ return value;
21
+ }
22
+ switch (typeof value) {
23
+ case `string`:
24
+ case `boolean`:
25
+ return value;
26
+ case `number`:
27
+ return Number.isFinite(value) ? value : null;
28
+ case `bigint`:
29
+ return { __type: `bigint`, value: value.toString() };
30
+ case `function`:
31
+ case `symbol`:
32
+ return void 0;
33
+ case `undefined`:
34
+ return void 0;
35
+ }
36
+ if (Array.isArray(value)) {
37
+ return value.map((entry) => toSerializableIndexValue(entry) ?? null);
38
+ }
39
+ if (value instanceof Date) {
40
+ return {
41
+ __type: `date`,
42
+ value: value.toISOString()
43
+ };
44
+ }
45
+ if (value instanceof Set) {
46
+ const serializedValues = Array.from(value).map((entry) => toSerializableIndexValue(entry) ?? null).sort(
47
+ (a, b) => compareStringsCodePoint(
48
+ stableStringifyCollectionIndexValue(a),
49
+ stableStringifyCollectionIndexValue(b)
50
+ )
51
+ );
52
+ return {
53
+ __type: `set`,
54
+ values: serializedValues
55
+ };
56
+ }
57
+ if (value instanceof Map) {
58
+ const serializedEntries = Array.from(value.entries()).map(([mapKey, mapValue]) => ({
59
+ key: toSerializableIndexValue(mapKey) ?? null,
60
+ value: toSerializableIndexValue(mapValue) ?? null
61
+ })).sort(
62
+ (a, b) => compareStringsCodePoint(
63
+ stableStringifyCollectionIndexValue(a.key),
64
+ stableStringifyCollectionIndexValue(b.key)
65
+ )
66
+ );
67
+ return {
68
+ __type: `map`,
69
+ entries: serializedEntries
70
+ };
71
+ }
72
+ if (value instanceof RegExp) {
73
+ return {
74
+ __type: `regexp`,
75
+ value: value.toString()
76
+ };
77
+ }
78
+ const serializedObject = {};
79
+ const entries = Object.entries(value).sort(
80
+ ([leftKey], [rightKey]) => compareStringsCodePoint(leftKey, rightKey)
81
+ );
82
+ for (const [key, entryValue] of entries) {
83
+ const serializedEntry = toSerializableIndexValue(entryValue);
84
+ if (serializedEntry !== void 0) {
85
+ serializedObject[key] = serializedEntry;
86
+ }
87
+ }
88
+ return serializedObject;
89
+ }
90
+ function stableStringifyCollectionIndexValue(value) {
91
+ if (value === null) {
92
+ return `null`;
93
+ }
94
+ if (Array.isArray(value)) {
95
+ return `[${value.map(stableStringifyCollectionIndexValue).join(`,`)}]`;
96
+ }
97
+ if (typeof value !== `object`) {
98
+ return JSON.stringify(value);
99
+ }
100
+ const sortedKeys = Object.keys(value).sort(
101
+ (left, right) => compareStringsCodePoint(left, right)
102
+ );
103
+ const serializedEntries = sortedKeys.map(
104
+ (key) => `${JSON.stringify(key)}:${stableStringifyCollectionIndexValue(value[key])}`
105
+ );
106
+ return `{${serializedEntries.join(`,`)}}`;
107
+ }
108
+ function createCollectionIndexMetadata(indexId, expression, name, resolver, options) {
109
+ const resolverMetadata = resolveResolverMetadata(resolver);
110
+ const serializedExpression = toSerializableIndexValue(expression) ?? null;
111
+ const serializedOptions = toSerializableIndexValue(options);
112
+ const signatureInput = toSerializableIndexValue({
113
+ signatureVersion: INDEX_SIGNATURE_VERSION,
114
+ expression: serializedExpression,
115
+ options: serializedOptions ?? null
116
+ });
117
+ const normalizedSignatureInput = signatureInput ?? null;
118
+ const signature = stableStringifyCollectionIndexValue(
119
+ normalizedSignatureInput
120
+ );
121
+ return {
122
+ signatureVersion: INDEX_SIGNATURE_VERSION,
123
+ signature,
124
+ indexId,
125
+ name,
126
+ expression,
127
+ resolver: resolverMetadata,
128
+ ...serializedOptions === void 0 ? {} : { options: serializedOptions }
129
+ };
130
+ }
131
+ function cloneSerializableIndexValue(value) {
132
+ if (value === null || typeof value !== `object`) {
133
+ return value;
134
+ }
135
+ if (Array.isArray(value)) {
136
+ return value.map((entry) => cloneSerializableIndexValue(entry));
137
+ }
138
+ const cloned = {};
139
+ for (const [key, entryValue] of Object.entries(value)) {
140
+ cloned[key] = cloneSerializableIndexValue(entryValue);
141
+ }
142
+ return cloned;
143
+ }
144
+ function cloneExpression(expression) {
145
+ return JSON.parse(JSON.stringify(expression));
146
+ }
6
147
  class CollectionIndexesManager {
7
148
  constructor() {
8
- this.lazyIndexes = /* @__PURE__ */ new Map();
9
- this.resolvedIndexes = /* @__PURE__ */ new Map();
10
- this.isIndexesResolved = false;
149
+ this.indexes = /* @__PURE__ */ new Map();
150
+ this.indexMetadata = /* @__PURE__ */ new Map();
11
151
  this.indexCounter = 0;
12
152
  }
13
153
  setDeps(deps) {
14
154
  this.state = deps.state;
15
155
  this.lifecycle = deps.lifecycle;
156
+ this.defaultIndexType = deps.defaultIndexType;
157
+ this.events = deps.events;
16
158
  }
17
159
  /**
18
160
  * Creates an index on a collection for faster queries.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * // With explicit index type (recommended for tree-shaking)
165
+ * import { BasicIndex } from '@tanstack/db'
166
+ * collection.createIndex((row) => row.userId, { indexType: BasicIndex })
167
+ *
168
+ * // With collection's default index type
169
+ * collection.createIndex((row) => row.userId)
170
+ * ```
19
171
  */
20
172
  createIndex(indexCallback, config = {}) {
21
173
  this.lifecycle.validateCollectionUsable(`createIndex`);
@@ -23,75 +175,73 @@ class CollectionIndexesManager {
23
175
  const singleRowRefProxy = refProxy.createSingleRowRefProxy();
24
176
  const indexExpression = indexCallback(singleRowRefProxy);
25
177
  const expression = refProxy.toExpression(indexExpression);
26
- const resolver = config.indexType ?? btreeIndex.BTreeIndex;
27
- const lazyIndex$1 = new lazyIndex.LazyIndexWrapper(
178
+ const IndexType = config.indexType ?? this.defaultIndexType;
179
+ if (!IndexType) {
180
+ throw new errors.CollectionConfigurationError(
181
+ `No index type specified and no defaultIndexType set on collection. Either pass indexType in config, or set defaultIndexType on the collection:
182
+ import { BasicIndex } from '@tanstack/db'
183
+ createCollection({ defaultIndexType: BasicIndex, ... })`
184
+ );
185
+ }
186
+ const index = new IndexType(
28
187
  indexId,
29
188
  expression,
30
189
  config.name,
31
- resolver,
32
- config.options,
33
- this.state.entries()
190
+ config.options
34
191
  );
35
- this.lazyIndexes.set(indexId, lazyIndex$1);
36
- if (resolver === btreeIndex.BTreeIndex) {
37
- try {
38
- const resolvedIndex = lazyIndex$1.getResolved();
39
- this.resolvedIndexes.set(indexId, resolvedIndex);
40
- } catch (error) {
41
- console.warn(`Failed to resolve BTreeIndex:`, error);
42
- }
43
- } else if (typeof resolver === `function` && resolver.prototype) {
44
- try {
45
- const resolvedIndex = lazyIndex$1.getResolved();
46
- this.resolvedIndexes.set(indexId, resolvedIndex);
47
- } catch {
48
- this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
49
- console.warn(`Failed to resolve single index:`, error);
50
- });
51
- }
52
- } else if (this.isIndexesResolved) {
53
- this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
54
- console.warn(`Failed to resolve single index:`, error);
55
- });
56
- }
57
- return new lazyIndex.IndexProxy(indexId, lazyIndex$1);
58
- }
59
- /**
60
- * Resolve all lazy indexes (called when collection first syncs)
61
- */
62
- async resolveAllIndexes() {
63
- if (this.isIndexesResolved) return;
64
- const resolutionPromises = Array.from(this.lazyIndexes.entries()).map(
65
- async ([indexId, lazyIndex2]) => {
66
- const resolvedIndex = await lazyIndex2.resolve();
67
- resolvedIndex.build(this.state.entries());
68
- this.resolvedIndexes.set(indexId, resolvedIndex);
69
- return { indexId, resolvedIndex };
70
- }
192
+ index.build(this.state.entries());
193
+ this.indexes.set(indexId, index);
194
+ const metadata = createCollectionIndexMetadata(
195
+ indexId,
196
+ expression,
197
+ config.name,
198
+ IndexType,
199
+ config.options
71
200
  );
72
- await Promise.all(resolutionPromises);
73
- this.isIndexesResolved = true;
201
+ this.indexMetadata.set(indexId, metadata);
202
+ this.events.emitIndexAdded(metadata);
203
+ return index;
74
204
  }
75
205
  /**
76
- * Resolve a single index immediately
206
+ * Removes an index from this collection.
207
+ * Returns true when an index existed and was removed, false otherwise.
77
208
  */
78
- async resolveSingleIndex(indexId, lazyIndex2) {
79
- const resolvedIndex = await lazyIndex2.resolve();
80
- resolvedIndex.build(this.state.entries());
81
- this.resolvedIndexes.set(indexId, resolvedIndex);
82
- return resolvedIndex;
209
+ removeIndex(indexOrId) {
210
+ this.lifecycle.validateCollectionUsable(`removeIndex`);
211
+ const indexId = typeof indexOrId === `number` ? indexOrId : indexOrId.id;
212
+ const index = this.indexes.get(indexId);
213
+ if (!index) {
214
+ return false;
215
+ }
216
+ if (typeof indexOrId !== `number` && index !== indexOrId) {
217
+ return false;
218
+ }
219
+ this.indexes.delete(indexId);
220
+ const metadata = this.indexMetadata.get(indexId);
221
+ this.indexMetadata.delete(indexId);
222
+ if (metadata) {
223
+ this.events.emitIndexRemoved(metadata);
224
+ }
225
+ return true;
83
226
  }
84
227
  /**
85
- * Get resolved indexes for query optimization
228
+ * Returns a sorted snapshot of index metadata.
229
+ * This allows persisted wrappers to bootstrap from indexes that were created
230
+ * before they attached lifecycle listeners.
86
231
  */
87
- get indexes() {
88
- return this.resolvedIndexes;
232
+ getIndexMetadataSnapshot() {
233
+ return Array.from(this.indexMetadata.values()).sort((left, right) => left.indexId - right.indexId).map((metadata) => ({
234
+ ...metadata,
235
+ expression: cloneExpression(metadata.expression),
236
+ resolver: { ...metadata.resolver },
237
+ ...metadata.options === void 0 ? {} : { options: cloneSerializableIndexValue(metadata.options) }
238
+ }));
89
239
  }
90
240
  /**
91
241
  * Updates all indexes when the collection changes
92
242
  */
93
243
  updateIndexes(changes) {
94
- for (const index of this.resolvedIndexes.values()) {
244
+ for (const index of this.indexes.values()) {
95
245
  for (const change of changes) {
96
246
  switch (change.type) {
97
247
  case `insert`:
@@ -112,12 +262,11 @@ class CollectionIndexesManager {
112
262
  }
113
263
  }
114
264
  /**
115
- * Clean up the collection by stopping sync and clearing data
116
- * This can be called manually or automatically by garbage collection
265
+ * Clean up indexes
117
266
  */
118
267
  cleanup() {
119
- this.lazyIndexes.clear();
120
- this.resolvedIndexes.clear();
268
+ this.indexes.clear();
269
+ this.indexMetadata.clear();
121
270
  }
122
271
  }
123
272
  exports.CollectionIndexesManager = CollectionIndexesManager;
@@ -1 +1 @@
1
- {"version":3,"file":"indexes.cjs","sources":["../../../src/collection/indexes.ts"],"sourcesContent":["import { IndexProxy, LazyIndexWrapper } from '../indexes/lazy-index'\nimport {\n createSingleRowRefProxy,\n toExpression,\n} from '../query/builder/ref-proxy'\nimport { BTreeIndex } from '../indexes/btree-index'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { BaseIndex, IndexResolver } from '../indexes/base-index'\nimport type { ChangeMessage } from '../types'\nimport type { IndexOptions } from '../indexes/index-options'\nimport type { SingleRowRefProxy } from '../query/builder/ref-proxy'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionIndexesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public lazyIndexes = new Map<number, LazyIndexWrapper<TKey>>()\n public resolvedIndexes = new Map<number, BaseIndex<TKey>>()\n public isIndexesResolved = false\n public indexCounter = 0\n\n constructor() {}\n\n setDeps(deps: {\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.state = deps.state\n this.lifecycle = deps.lifecycle\n }\n\n /**\n * Creates an index on a collection for faster queries.\n */\n public createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(\n indexCallback: (row: SingleRowRefProxy<TOutput>) => any,\n config: IndexOptions<TResolver> = {},\n ): IndexProxy<TKey> {\n this.lifecycle.validateCollectionUsable(`createIndex`)\n\n const indexId = ++this.indexCounter\n const singleRowRefProxy = createSingleRowRefProxy<TOutput>()\n const indexExpression = indexCallback(singleRowRefProxy)\n const expression = toExpression(indexExpression)\n\n // Default to BTreeIndex if no type specified\n const resolver = config.indexType ?? (BTreeIndex as unknown as TResolver)\n\n // Create lazy wrapper\n const lazyIndex = new LazyIndexWrapper<TKey>(\n indexId,\n expression,\n config.name,\n resolver,\n config.options,\n this.state.entries(),\n )\n\n this.lazyIndexes.set(indexId, lazyIndex)\n\n // For BTreeIndex, resolve immediately and synchronously\n if ((resolver as unknown) === BTreeIndex) {\n try {\n const resolvedIndex = lazyIndex.getResolved()\n this.resolvedIndexes.set(indexId, resolvedIndex)\n } catch (error) {\n console.warn(`Failed to resolve BTreeIndex:`, error)\n }\n } else if (typeof resolver === `function` && resolver.prototype) {\n // Other synchronous constructors - resolve immediately\n try {\n const resolvedIndex = lazyIndex.getResolved()\n this.resolvedIndexes.set(indexId, resolvedIndex)\n } catch {\n // Fallback to async resolution\n this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {\n console.warn(`Failed to resolve single index:`, error)\n })\n }\n } else if (this.isIndexesResolved) {\n // Async loader but indexes are already resolved - resolve this one\n this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {\n console.warn(`Failed to resolve single index:`, error)\n })\n }\n\n return new IndexProxy(indexId, lazyIndex)\n }\n\n /**\n * Resolve all lazy indexes (called when collection first syncs)\n */\n public async resolveAllIndexes(): Promise<void> {\n if (this.isIndexesResolved) return\n\n const resolutionPromises = Array.from(this.lazyIndexes.entries()).map(\n async ([indexId, lazyIndex]) => {\n const resolvedIndex = await lazyIndex.resolve()\n\n // Build index with current data\n resolvedIndex.build(this.state.entries())\n\n this.resolvedIndexes.set(indexId, resolvedIndex)\n return { indexId, resolvedIndex }\n },\n )\n\n await Promise.all(resolutionPromises)\n this.isIndexesResolved = true\n }\n\n /**\n * Resolve a single index immediately\n */\n private async resolveSingleIndex(\n indexId: number,\n lazyIndex: LazyIndexWrapper<TKey>,\n ): Promise<BaseIndex<TKey>> {\n const resolvedIndex = await lazyIndex.resolve()\n resolvedIndex.build(this.state.entries())\n this.resolvedIndexes.set(indexId, resolvedIndex)\n return resolvedIndex\n }\n\n /**\n * Get resolved indexes for query optimization\n */\n get indexes(): Map<number, BaseIndex<TKey>> {\n return this.resolvedIndexes\n }\n\n /**\n * Updates all indexes when the collection changes\n */\n public updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {\n for (const index of this.resolvedIndexes.values()) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n index.add(change.key, change.value)\n break\n case `update`:\n if (change.previousValue) {\n index.update(change.key, change.previousValue, change.value)\n } else {\n index.add(change.key, change.value)\n }\n break\n case `delete`:\n index.remove(change.key, change.value)\n break\n }\n }\n }\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public cleanup(): void {\n this.lazyIndexes.clear()\n this.resolvedIndexes.clear()\n }\n}\n"],"names":["createSingleRowRefProxy","toExpression","BTreeIndex","lazyIndex","LazyIndexWrapper","IndexProxy"],"mappings":"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"}
1
+ {"version":3,"file":"indexes.cjs","sources":["../../../src/collection/indexes.ts"],"sourcesContent":["import {\n createSingleRowRefProxy,\n toExpression,\n} from '../query/builder/ref-proxy'\nimport { CollectionConfigurationError } from '../errors'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { BaseIndex, IndexConstructor } from '../indexes/base-index'\nimport type { ChangeMessage } from '../types'\nimport type { IndexOptions } from '../indexes/index-options'\nimport type { SingleRowRefProxy } from '../query/builder/ref-proxy'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionStateManager } from './state'\nimport type { BasicExpression } from '../query/ir'\nimport type {\n CollectionEventsManager,\n CollectionIndexMetadata,\n CollectionIndexResolverMetadata,\n CollectionIndexSerializableValue,\n} from './events'\n\nconst INDEX_SIGNATURE_VERSION = 1 as const\n\nfunction compareStringsCodePoint(left: string, right: string): number {\n if (left === right) {\n return 0\n }\n\n return left < right ? -1 : 1\n}\n\nfunction resolveResolverMetadata<TKey extends string | number>(\n resolver: IndexConstructor<TKey>,\n): CollectionIndexResolverMetadata {\n return {\n kind: `constructor`,\n ...(resolver.name ? { name: resolver.name } : {}),\n }\n}\n\nfunction toSerializableIndexValue(\n value: unknown,\n): CollectionIndexSerializableValue | undefined {\n if (value == null) {\n return value\n }\n\n switch (typeof value) {\n case `string`:\n case `boolean`:\n return value\n case `number`:\n return Number.isFinite(value) ? value : null\n case `bigint`:\n return { __type: `bigint`, value: value.toString() }\n case `function`:\n case `symbol`:\n // Function and symbol identity are process-local and not stable across runtimes.\n // Dropping them keeps signatures deterministic; we may skip index reuse, which is acceptable.\n return undefined\n case `undefined`:\n return undefined\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => toSerializableIndexValue(entry) ?? null)\n }\n\n if (value instanceof Date) {\n return {\n __type: `date`,\n value: value.toISOString(),\n }\n }\n\n if (value instanceof Set) {\n const serializedValues = Array.from(value)\n .map((entry) => toSerializableIndexValue(entry) ?? null)\n .sort((a, b) =>\n compareStringsCodePoint(\n stableStringifyCollectionIndexValue(a),\n stableStringifyCollectionIndexValue(b),\n ),\n )\n return {\n __type: `set`,\n values: serializedValues,\n }\n }\n\n if (value instanceof Map) {\n const serializedEntries = Array.from(value.entries())\n .map(([mapKey, mapValue]) => ({\n key: toSerializableIndexValue(mapKey) ?? null,\n value: toSerializableIndexValue(mapValue) ?? null,\n }))\n .sort((a, b) =>\n compareStringsCodePoint(\n stableStringifyCollectionIndexValue(a.key),\n stableStringifyCollectionIndexValue(b.key),\n ),\n )\n\n return {\n __type: `map`,\n entries: serializedEntries,\n }\n }\n\n if (value instanceof RegExp) {\n return {\n __type: `regexp`,\n value: value.toString(),\n }\n }\n\n const serializedObject: Record<string, CollectionIndexSerializableValue> = {}\n const entries = Object.entries(value as Record<string, unknown>).sort(\n ([leftKey], [rightKey]) => compareStringsCodePoint(leftKey, rightKey),\n )\n\n for (const [key, entryValue] of entries) {\n const serializedEntry = toSerializableIndexValue(entryValue)\n if (serializedEntry !== undefined) {\n serializedObject[key] = serializedEntry\n }\n }\n\n return serializedObject\n}\n\nfunction stableStringifyCollectionIndexValue(\n value: CollectionIndexSerializableValue,\n): string {\n if (value === null) {\n return `null`\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringifyCollectionIndexValue).join(`,`)}]`\n }\n\n if (typeof value !== `object`) {\n return JSON.stringify(value)\n }\n\n const sortedKeys = Object.keys(value).sort((left, right) =>\n compareStringsCodePoint(left, right),\n )\n const serializedEntries = sortedKeys.map(\n (key) =>\n `${JSON.stringify(key)}:${stableStringifyCollectionIndexValue(value[key]!)}`,\n )\n return `{${serializedEntries.join(`,`)}}`\n}\n\nfunction createCollectionIndexMetadata<TKey extends string | number>(\n indexId: number,\n expression: BasicExpression,\n name: string | undefined,\n resolver: IndexConstructor<TKey>,\n options: unknown,\n): CollectionIndexMetadata {\n const resolverMetadata = resolveResolverMetadata(resolver)\n const serializedExpression = toSerializableIndexValue(expression) ?? null\n const serializedOptions = toSerializableIndexValue(options)\n const signatureInput = toSerializableIndexValue({\n signatureVersion: INDEX_SIGNATURE_VERSION,\n expression: serializedExpression,\n options: serializedOptions ?? null,\n })\n const normalizedSignatureInput = signatureInput ?? null\n const signature = stableStringifyCollectionIndexValue(\n normalizedSignatureInput,\n )\n\n return {\n signatureVersion: INDEX_SIGNATURE_VERSION,\n signature,\n indexId,\n name,\n expression,\n resolver: resolverMetadata,\n ...(serializedOptions === undefined ? {} : { options: serializedOptions }),\n }\n}\n\nfunction cloneSerializableIndexValue(\n value: CollectionIndexSerializableValue,\n): CollectionIndexSerializableValue {\n if (value === null || typeof value !== `object`) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => cloneSerializableIndexValue(entry))\n }\n\n const cloned: Record<string, CollectionIndexSerializableValue> = {}\n for (const [key, entryValue] of Object.entries(value)) {\n cloned[key] = cloneSerializableIndexValue(entryValue)\n }\n return cloned\n}\n\nfunction cloneExpression(expression: BasicExpression): BasicExpression {\n return JSON.parse(JSON.stringify(expression)) as BasicExpression\n}\n\nexport class CollectionIndexesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n private defaultIndexType: IndexConstructor<TKey> | undefined\n private events!: CollectionEventsManager\n\n public indexes = new Map<number, BaseIndex<TKey>>()\n public indexMetadata = new Map<number, CollectionIndexMetadata>()\n public indexCounter = 0\n\n constructor() {}\n\n setDeps(deps: {\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n defaultIndexType?: IndexConstructor<TKey>\n events: CollectionEventsManager\n }) {\n this.state = deps.state\n this.lifecycle = deps.lifecycle\n this.defaultIndexType = deps.defaultIndexType\n this.events = deps.events\n }\n\n /**\n * Creates an index on a collection for faster queries.\n *\n * @example\n * ```ts\n * // With explicit index type (recommended for tree-shaking)\n * import { BasicIndex } from '@tanstack/db'\n * collection.createIndex((row) => row.userId, { indexType: BasicIndex })\n *\n * // With collection's default index type\n * collection.createIndex((row) => row.userId)\n * ```\n */\n public createIndex<TIndexType extends IndexConstructor<TKey>>(\n indexCallback: (row: SingleRowRefProxy<TOutput>) => any,\n config: IndexOptions<TIndexType> = {},\n ): BaseIndex<TKey> {\n this.lifecycle.validateCollectionUsable(`createIndex`)\n\n const indexId = ++this.indexCounter\n const singleRowRefProxy = createSingleRowRefProxy<TOutput>()\n const indexExpression = indexCallback(singleRowRefProxy)\n const expression = toExpression(indexExpression)\n\n // Use provided index type, or fall back to collection's default\n const IndexType = config.indexType ?? this.defaultIndexType\n if (!IndexType) {\n throw new CollectionConfigurationError(\n `No index type specified and no defaultIndexType set on collection. ` +\n `Either pass indexType in config, or set defaultIndexType on the collection:\\n` +\n ` import { BasicIndex } from '@tanstack/db'\\n` +\n ` createCollection({ defaultIndexType: BasicIndex, ... })`,\n )\n }\n\n // Create index synchronously\n const index = new IndexType(\n indexId,\n expression,\n config.name,\n config.options,\n )\n\n // Build with current data\n index.build(this.state.entries())\n\n this.indexes.set(indexId, index)\n\n // Track metadata and emit event\n const metadata = createCollectionIndexMetadata(\n indexId,\n expression,\n config.name,\n IndexType,\n config.options,\n )\n this.indexMetadata.set(indexId, metadata)\n this.events.emitIndexAdded(metadata)\n\n return index\n }\n\n /**\n * Removes an index from this collection.\n * Returns true when an index existed and was removed, false otherwise.\n */\n public removeIndex(indexOrId: BaseIndex<TKey> | number): boolean {\n this.lifecycle.validateCollectionUsable(`removeIndex`)\n\n const indexId = typeof indexOrId === `number` ? indexOrId : indexOrId.id\n const index = this.indexes.get(indexId)\n if (!index) {\n return false\n }\n\n if (typeof indexOrId !== `number` && index !== indexOrId) {\n // Passed a different index instance with the same id — do not remove.\n return false\n }\n\n this.indexes.delete(indexId)\n\n const metadata = this.indexMetadata.get(indexId)\n this.indexMetadata.delete(indexId)\n if (metadata) {\n this.events.emitIndexRemoved(metadata)\n }\n\n return true\n }\n\n /**\n * Returns a sorted snapshot of index metadata.\n * This allows persisted wrappers to bootstrap from indexes that were created\n * before they attached lifecycle listeners.\n */\n public getIndexMetadataSnapshot(): Array<CollectionIndexMetadata> {\n return Array.from(this.indexMetadata.values())\n .sort((left, right) => left.indexId - right.indexId)\n .map((metadata) => ({\n ...metadata,\n expression: cloneExpression(metadata.expression),\n resolver: { ...metadata.resolver },\n ...(metadata.options === undefined\n ? {}\n : { options: cloneSerializableIndexValue(metadata.options) }),\n }))\n }\n\n /**\n * Updates all indexes when the collection changes\n */\n public updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {\n for (const index of this.indexes.values()) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n index.add(change.key, change.value)\n break\n case `update`:\n if (change.previousValue) {\n index.update(change.key, change.previousValue, change.value)\n } else {\n index.add(change.key, change.value)\n }\n break\n case `delete`:\n index.remove(change.key, change.value)\n break\n }\n }\n }\n }\n\n /**\n * Clean up indexes\n */\n public cleanup(): void {\n this.indexes.clear()\n this.indexMetadata.clear()\n }\n}\n"],"names":["createSingleRowRefProxy","toExpression","CollectionConfigurationError"],"mappings":";;;;AAoBA,MAAM,0BAA0B;AAEhC,SAAS,wBAAwB,MAAc,OAAuB;AACpE,MAAI,SAAS,OAAO;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,QAAQ,KAAK;AAC7B;AAEA,SAAS,wBACP,UACiC;AACjC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,GAAI,SAAS,OAAO,EAAE,MAAM,SAAS,KAAA,IAAS,CAAA;AAAA,EAAC;AAEnD;AAEA,SAAS,yBACP,OAC8C;AAC9C,MAAI,SAAS,MAAM;AACjB,WAAO;AAAA,EACT;AAEA,UAAQ,OAAO,OAAA;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,IAC1C,KAAK;AACH,aAAO,EAAE,QAAQ,UAAU,OAAO,MAAM,WAAS;AAAA,IACnD,KAAK;AAAA,IACL,KAAK;AAGH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EAAA;AAGX,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,UAAU,yBAAyB,KAAK,KAAK,IAAI;AAAA,EACrE;AAEA,MAAI,iBAAiB,MAAM;AACzB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO,MAAM,YAAA;AAAA,IAAY;AAAA,EAE7B;AAEA,MAAI,iBAAiB,KAAK;AACxB,UAAM,mBAAmB,MAAM,KAAK,KAAK,EACtC,IAAI,CAAC,UAAU,yBAAyB,KAAK,KAAK,IAAI,EACtD;AAAA,MAAK,CAAC,GAAG,MACR;AAAA,QACE,oCAAoC,CAAC;AAAA,QACrC,oCAAoC,CAAC;AAAA,MAAA;AAAA,IACvC;AAEJ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA;AAAA,EAEZ;AAEA,MAAI,iBAAiB,KAAK;AACxB,UAAM,oBAAoB,MAAM,KAAK,MAAM,SAAS,EACjD,IAAI,CAAC,CAAC,QAAQ,QAAQ,OAAO;AAAA,MAC5B,KAAK,yBAAyB,MAAM,KAAK;AAAA,MACzC,OAAO,yBAAyB,QAAQ,KAAK;AAAA,IAAA,EAC7C,EACD;AAAA,MAAK,CAAC,GAAG,MACR;AAAA,QACE,oCAAoC,EAAE,GAAG;AAAA,QACzC,oCAAoC,EAAE,GAAG;AAAA,MAAA;AAAA,IAC3C;AAGJ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,IAAA;AAAA,EAEb;AAEA,MAAI,iBAAiB,QAAQ;AAC3B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO,MAAM,SAAA;AAAA,IAAS;AAAA,EAE1B;AAEA,QAAM,mBAAqE,CAAA;AAC3E,QAAM,UAAU,OAAO,QAAQ,KAAgC,EAAE;AAAA,IAC/D,CAAC,CAAC,OAAO,GAAG,CAAC,QAAQ,MAAM,wBAAwB,SAAS,QAAQ;AAAA,EAAA;AAGtE,aAAW,CAAC,KAAK,UAAU,KAAK,SAAS;AACvC,UAAM,kBAAkB,yBAAyB,UAAU;AAC3D,QAAI,oBAAoB,QAAW;AACjC,uBAAiB,GAAG,IAAI;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,oCACP,OACQ;AACR,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,IAAI,MAAM,IAAI,mCAAmC,EAAE,KAAK,GAAG,CAAC;AAAA,EACrE;AAEA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AAEA,QAAM,aAAa,OAAO,KAAK,KAAK,EAAE;AAAA,IAAK,CAAC,MAAM,UAChD,wBAAwB,MAAM,KAAK;AAAA,EAAA;AAErC,QAAM,oBAAoB,WAAW;AAAA,IACnC,CAAC,QACC,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,oCAAoC,MAAM,GAAG,CAAE,CAAC;AAAA,EAAA;AAE9E,SAAO,IAAI,kBAAkB,KAAK,GAAG,CAAC;AACxC;AAEA,SAAS,8BACP,SACA,YACA,MACA,UACA,SACyB;AACzB,QAAM,mBAAmB,wBAAwB,QAAQ;AACzD,QAAM,uBAAuB,yBAAyB,UAAU,KAAK;AACrE,QAAM,oBAAoB,yBAAyB,OAAO;AAC1D,QAAM,iBAAiB,yBAAyB;AAAA,IAC9C,kBAAkB;AAAA,IAClB,YAAY;AAAA,IACZ,SAAS,qBAAqB;AAAA,EAAA,CAC/B;AACD,QAAM,2BAA2B,kBAAkB;AACnD,QAAM,YAAY;AAAA,IAChB;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,GAAI,sBAAsB,SAAY,KAAK,EAAE,SAAS,kBAAA;AAAA,EAAkB;AAE5E;AAEA,SAAS,4BACP,OACkC;AAClC,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,UAAU,4BAA4B,KAAK,CAAC;AAAA,EAChE;AAEA,QAAM,SAA2D,CAAA;AACjE,aAAW,CAAC,KAAK,UAAU,KAAK,OAAO,QAAQ,KAAK,GAAG;AACrD,WAAO,GAAG,IAAI,4BAA4B,UAAU;AAAA,EACtD;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,YAA8C;AACrE,SAAO,KAAK,MAAM,KAAK,UAAU,UAAU,CAAC;AAC9C;AAEO,MAAM,yBAKX;AAAA,EAUA,cAAc;AAJd,SAAO,8BAAc,IAAA;AACrB,SAAO,oCAAoB,IAAA;AAC3B,SAAO,eAAe;AAAA,EAEP;AAAA,EAEf,QAAQ,MAKL;AACD,SAAK,QAAQ,KAAK;AAClB,SAAK,YAAY,KAAK;AACtB,SAAK,mBAAmB,KAAK;AAC7B,SAAK,SAAS,KAAK;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeO,YACL,eACA,SAAmC,IAClB;AACjB,SAAK,UAAU,yBAAyB,aAAa;AAErD,UAAM,UAAU,EAAE,KAAK;AACvB,UAAM,oBAAoBA,SAAAA,wBAAA;AAC1B,UAAM,kBAAkB,cAAc,iBAAiB;AACvD,UAAM,aAAaC,SAAAA,aAAa,eAAe;AAG/C,UAAM,YAAY,OAAO,aAAa,KAAK;AAC3C,QAAI,CAAC,WAAW;AACd,YAAM,IAAIC,OAAAA;AAAAA,QACR;AAAA;AAAA;AAAA,MAAA;AAAA,IAKJ;AAGA,UAAM,QAAQ,IAAI;AAAA,MAChB;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAIT,UAAM,MAAM,KAAK,MAAM,QAAA,CAAS;AAEhC,SAAK,QAAQ,IAAI,SAAS,KAAK;AAG/B,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,OAAO;AAAA,IAAA;AAET,SAAK,cAAc,IAAI,SAAS,QAAQ;AACxC,SAAK,OAAO,eAAe,QAAQ;AAEnC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,YAAY,WAA8C;AAC/D,SAAK,UAAU,yBAAyB,aAAa;AAErD,UAAM,UAAU,OAAO,cAAc,WAAW,YAAY,UAAU;AACtE,UAAM,QAAQ,KAAK,QAAQ,IAAI,OAAO;AACtC,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,cAAc,YAAY,UAAU,WAAW;AAExD,aAAO;AAAA,IACT;AAEA,SAAK,QAAQ,OAAO,OAAO;AAE3B,UAAM,WAAW,KAAK,cAAc,IAAI,OAAO;AAC/C,SAAK,cAAc,OAAO,OAAO;AACjC,QAAI,UAAU;AACZ,WAAK,OAAO,iBAAiB,QAAQ;AAAA,IACvC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,2BAA2D;AAChE,WAAO,MAAM,KAAK,KAAK,cAAc,OAAA,CAAQ,EAC1C,KAAK,CAAC,MAAM,UAAU,KAAK,UAAU,MAAM,OAAO,EAClD,IAAI,CAAC,cAAc;AAAA,MAClB,GAAG;AAAA,MACH,YAAY,gBAAgB,SAAS,UAAU;AAAA,MAC/C,UAAU,EAAE,GAAG,SAAS,SAAA;AAAA,MACxB,GAAI,SAAS,YAAY,SACrB,CAAA,IACA,EAAE,SAAS,4BAA4B,SAAS,OAAO,EAAA;AAAA,IAAE,EAC7D;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKO,cAAc,SAAoD;AACvE,eAAW,SAAS,KAAK,QAAQ,OAAA,GAAU;AACzC,iBAAW,UAAU,SAAS;AAC5B,gBAAQ,OAAO,MAAA;AAAA,UACb,KAAK;AACH,kBAAM,IAAI,OAAO,KAAK,OAAO,KAAK;AAClC;AAAA,UACF,KAAK;AACH,gBAAI,OAAO,eAAe;AACxB,oBAAM,OAAO,OAAO,KAAK,OAAO,eAAe,OAAO,KAAK;AAAA,YAC7D,OAAO;AACL,oBAAM,IAAI,OAAO,KAAK,OAAO,KAAK;AAAA,YACpC;AACA;AAAA,UACF,KAAK;AACH,kBAAM,OAAO,OAAO,KAAK,OAAO,KAAK;AACrC;AAAA,QAAA;AAAA,MAEN;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,UAAgB;AACrB,SAAK,QAAQ,MAAA;AACb,SAAK,cAAc,MAAA;AAAA,EACrB;AACF;;"}
@@ -1,47 +1,57 @@
1
- import { IndexProxy, LazyIndexWrapper } from '../indexes/lazy-index.cjs';
2
- import { BTreeIndex } from '../indexes/btree-index.cjs';
3
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
4
- import { BaseIndex, IndexResolver } from '../indexes/base-index.cjs';
2
+ import { BaseIndex, IndexConstructor } from '../indexes/base-index.cjs';
5
3
  import { ChangeMessage } from '../types.cjs';
6
4
  import { IndexOptions } from '../indexes/index-options.cjs';
7
5
  import { SingleRowRefProxy } from '../query/builder/ref-proxy.cjs';
8
6
  import { CollectionLifecycleManager } from './lifecycle.cjs';
9
7
  import { CollectionStateManager } from './state.cjs';
8
+ import { CollectionEventsManager, CollectionIndexMetadata } from './events.cjs';
10
9
  export declare class CollectionIndexesManager<TOutput extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInput extends object = TOutput> {
11
10
  private lifecycle;
12
11
  private state;
13
- lazyIndexes: Map<number, LazyIndexWrapper<TKey>>;
14
- resolvedIndexes: Map<number, BaseIndex<TKey>>;
15
- isIndexesResolved: boolean;
12
+ private defaultIndexType;
13
+ private events;
14
+ indexes: Map<number, BaseIndex<TKey>>;
15
+ indexMetadata: Map<number, CollectionIndexMetadata>;
16
16
  indexCounter: number;
17
17
  constructor();
18
18
  setDeps(deps: {
19
19
  state: CollectionStateManager<TOutput, TKey, TSchema, TInput>;
20
20
  lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>;
21
+ defaultIndexType?: IndexConstructor<TKey>;
22
+ events: CollectionEventsManager;
21
23
  }): void;
22
24
  /**
23
25
  * Creates an index on a collection for faster queries.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // With explicit index type (recommended for tree-shaking)
30
+ * import { BasicIndex } from '@tanstack/db'
31
+ * collection.createIndex((row) => row.userId, { indexType: BasicIndex })
32
+ *
33
+ * // With collection's default index type
34
+ * collection.createIndex((row) => row.userId)
35
+ * ```
24
36
  */
25
- createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(indexCallback: (row: SingleRowRefProxy<TOutput>) => any, config?: IndexOptions<TResolver>): IndexProxy<TKey>;
37
+ createIndex<TIndexType extends IndexConstructor<TKey>>(indexCallback: (row: SingleRowRefProxy<TOutput>) => any, config?: IndexOptions<TIndexType>): BaseIndex<TKey>;
26
38
  /**
27
- * Resolve all lazy indexes (called when collection first syncs)
39
+ * Removes an index from this collection.
40
+ * Returns true when an index existed and was removed, false otherwise.
28
41
  */
29
- resolveAllIndexes(): Promise<void>;
42
+ removeIndex(indexOrId: BaseIndex<TKey> | number): boolean;
30
43
  /**
31
- * Resolve a single index immediately
44
+ * Returns a sorted snapshot of index metadata.
45
+ * This allows persisted wrappers to bootstrap from indexes that were created
46
+ * before they attached lifecycle listeners.
32
47
  */
33
- private resolveSingleIndex;
34
- /**
35
- * Get resolved indexes for query optimization
36
- */
37
- get indexes(): Map<number, BaseIndex<TKey>>;
48
+ getIndexMetadataSnapshot(): Array<CollectionIndexMetadata>;
38
49
  /**
39
50
  * Updates all indexes when the collection changes
40
51
  */
41
52
  updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void;
42
53
  /**
43
- * Clean up the collection by stopping sync and clearing data
44
- * This can be called manually or automatically by garbage collection
54
+ * Clean up indexes
45
55
  */
46
56
  cleanup(): void;
47
57
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const errors = require("../errors.cjs");
4
4
  const browserPolyfills = require("../utils/browser-polyfills.cjs");
5
+ const cleanupQueue = require("./cleanup-queue.cjs");
5
6
  class CollectionLifecycleManager {
6
7
  /**
7
8
  * Creates a new CollectionLifecycleManager instance
@@ -11,7 +12,6 @@ class CollectionLifecycleManager {
11
12
  this.hasBeenReady = false;
12
13
  this.hasReceivedFirstCommit = false;
13
14
  this.onFirstReadyCallbacks = [];
14
- this.gcTimeoutId = null;
15
15
  this.idleCallbackId = null;
16
16
  this.config = config;
17
17
  this.id = id;
@@ -54,14 +54,6 @@ class CollectionLifecycleManager {
54
54
  this.validateStatusTransition(this.status, newStatus);
55
55
  const previousStatus = this.status;
56
56
  this.status = newStatus;
57
- if (newStatus === `ready` && !this.indexes.isIndexesResolved) {
58
- this.indexes.resolveAllIndexes().catch((error) => {
59
- console.warn(
60
- `${this.config.id ? `[${this.config.id}] ` : ``}Failed to resolve indexes:`,
61
- error
62
- );
63
- });
64
- }
65
57
  this.events.emitStatusChange(newStatus, previousStatus);
66
58
  }
67
59
  /**
@@ -106,28 +98,22 @@ class CollectionLifecycleManager {
106
98
  * Called when the collection becomes inactive (no subscribers)
107
99
  */
108
100
  startGCTimer() {
109
- if (this.gcTimeoutId) {
110
- clearTimeout(this.gcTimeoutId);
111
- }
112
101
  const gcTime = this.config.gcTime ?? 3e5;
113
102
  if (gcTime <= 0 || !Number.isFinite(gcTime)) {
114
103
  return;
115
104
  }
116
- this.gcTimeoutId = setTimeout(() => {
105
+ cleanupQueue.CleanupQueue.getInstance().schedule(this, gcTime, () => {
117
106
  if (this.changes.activeSubscribersCount === 0) {
118
107
  this.scheduleIdleCleanup();
119
108
  }
120
- }, gcTime);
109
+ });
121
110
  }
122
111
  /**
123
112
  * Cancel the garbage collection timer
124
113
  * Called when the collection becomes active again
125
114
  */
126
115
  cancelGCTimer() {
127
- if (this.gcTimeoutId) {
128
- clearTimeout(this.gcTimeoutId);
129
- this.gcTimeoutId = null;
130
- }
116
+ cleanupQueue.CleanupQueue.getInstance().cancel(this);
131
117
  if (this.idleCallbackId !== null) {
132
118
  browserPolyfills.safeCancelIdleCallback(this.idleCallbackId);
133
119
  this.idleCallbackId = null;
@@ -166,10 +152,7 @@ class CollectionLifecycleManager {
166
152
  this.state.cleanup();
167
153
  this.changes.cleanup();
168
154
  this.indexes.cleanup();
169
- if (this.gcTimeoutId) {
170
- clearTimeout(this.gcTimeoutId);
171
- this.gcTimeoutId = null;
172
- }
155
+ cleanupQueue.CleanupQueue.getInstance().cancel(this);
173
156
  this.hasBeenReady = false;
174
157
  const callbacks = [...this.onFirstReadyCallbacks];
175
158
  this.onFirstReadyCallbacks = [];
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.cjs","sources":["../../../src/collection/lifecycle.ts"],"sourcesContent":["import {\n CollectionInErrorStateError,\n CollectionStateError,\n InvalidCollectionStatusTransitionError,\n} from '../errors'\nimport {\n safeCancelIdleCallback,\n safeRequestIdleCallback,\n} from '../utils/browser-polyfills'\nimport type { IdleCallbackDeadline } from '../utils/browser-polyfills'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { CollectionConfig, CollectionStatus } from '../types'\nimport type { CollectionEventsManager } from './events'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionSyncManager } from './sync'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionLifecycleManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private config: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public status: CollectionStatus = `idle`\n public hasBeenReady = false\n public hasReceivedFirstCommit = false\n public onFirstReadyCallbacks: Array<() => void> = []\n public gcTimeoutId: ReturnType<typeof setTimeout> | null = null\n private idleCallbackId: number | null = null\n\n /**\n * Creates a new CollectionLifecycleManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n }\n\n setDeps(deps: {\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.indexes = deps.indexes\n this.events = deps.events\n this.changes = deps.changes\n this.sync = deps.sync\n this.state = deps.state\n }\n\n /**\n * Validates state transitions to prevent invalid status changes\n */\n public validateStatusTransition(\n from: CollectionStatus,\n to: CollectionStatus,\n ): void {\n if (from === to) {\n // Allow same state transitions\n return\n }\n const validTransitions: Record<\n CollectionStatus,\n Array<CollectionStatus>\n > = {\n idle: [`loading`, `error`, `cleaned-up`],\n loading: [`ready`, `error`, `cleaned-up`],\n ready: [`cleaned-up`, `error`],\n error: [`cleaned-up`, `idle`],\n 'cleaned-up': [`loading`, `error`],\n }\n\n if (!validTransitions[from].includes(to)) {\n throw new InvalidCollectionStatusTransitionError(from, to, this.id)\n }\n }\n\n /**\n * Safely update the collection status with validation\n * @private\n */\n public setStatus(\n newStatus: CollectionStatus,\n allowReady: boolean = false,\n ): void {\n if (newStatus === `ready` && !allowReady) {\n // setStatus('ready') is an internal method that should not be called directly\n // Instead, use markReady to transition to ready triggering the necessary events\n // and side effects.\n throw new CollectionStateError(\n `You can't directly call \"setStatus('ready'). You must use markReady instead.`,\n )\n }\n this.validateStatusTransition(this.status, newStatus)\n const previousStatus = this.status\n this.status = newStatus\n\n // Resolve indexes when collection becomes ready\n if (newStatus === `ready` && !this.indexes.isIndexesResolved) {\n // Resolve indexes asynchronously without blocking\n this.indexes.resolveAllIndexes().catch((error) => {\n console.warn(\n `${this.config.id ? `[${this.config.id}] ` : ``}Failed to resolve indexes:`,\n error,\n )\n })\n }\n\n // Emit event\n this.events.emitStatusChange(newStatus, previousStatus)\n }\n\n /**\n * Validates that the collection is in a usable state for data operations\n * @private\n */\n public validateCollectionUsable(operation: string): void {\n switch (this.status) {\n case `error`:\n throw new CollectionInErrorStateError(operation, this.id)\n case `cleaned-up`:\n // Automatically restart the collection when operations are called on cleaned-up collections\n this.sync.startSync()\n break\n }\n }\n\n /**\n * Mark the collection as ready for use\n * This is called by sync implementations to explicitly signal that the collection is ready,\n * providing a more intuitive alternative to using commits for readiness signaling\n * @private - Should only be called by sync implementations\n */\n public markReady(): void {\n this.validateStatusTransition(this.status, `ready`)\n // Can transition to ready from loading state\n if (this.status === `loading`) {\n this.setStatus(`ready`, true)\n\n // Call any registered first ready callbacks (only on first time becoming ready)\n if (!this.hasBeenReady) {\n this.hasBeenReady = true\n\n // Also mark as having received first commit for backwards compatibility\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n // Notify dependents when markReady is called, after status is set\n // This ensures live queries get notified when their dependencies become ready\n if (this.changes.changeSubscriptions.size > 0) {\n this.changes.emitEmptyReadyEvent()\n }\n }\n }\n\n /**\n * Start the garbage collection timer\n * Called when the collection becomes inactive (no subscribers)\n */\n public startGCTimer(): void {\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n }\n\n const gcTime = this.config.gcTime ?? 300000 // 5 minutes default\n\n // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.\n // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC,\n // so we must explicitly check for non-finite values here.\n if (gcTime <= 0 || !Number.isFinite(gcTime)) {\n return\n }\n\n this.gcTimeoutId = setTimeout(() => {\n if (this.changes.activeSubscribersCount === 0) {\n // Schedule cleanup during idle time to avoid blocking the UI thread\n this.scheduleIdleCleanup()\n }\n }, gcTime)\n }\n\n /**\n * Cancel the garbage collection timer\n * Called when the collection becomes active again\n */\n public cancelGCTimer(): void {\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n this.gcTimeoutId = null\n }\n // Also cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n }\n\n /**\n * Schedule cleanup to run during browser idle time\n * This prevents blocking the UI thread during cleanup operations\n */\n private scheduleIdleCleanup(): void {\n // Cancel any existing idle callback\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n }\n\n // Schedule cleanup with a timeout of 1 second\n // This ensures cleanup happens even if the browser is busy\n this.idleCallbackId = safeRequestIdleCallback(\n (deadline) => {\n // Perform cleanup if we still have no subscribers\n if (this.changes.activeSubscribersCount === 0) {\n const cleanupCompleted = this.performCleanup(deadline)\n // Only clear the callback ID if cleanup actually completed\n if (cleanupCompleted) {\n this.idleCallbackId = null\n }\n } else {\n // No need to cleanup, clear the callback ID\n this.idleCallbackId = null\n }\n },\n { timeout: 1000 },\n )\n }\n\n /**\n * Perform cleanup operations, optionally in chunks during idle time\n * @returns true if cleanup was completed, false if it was rescheduled\n */\n private performCleanup(deadline?: IdleCallbackDeadline): boolean {\n // If we have a deadline, we can potentially split cleanup into chunks\n // For now, we'll do all cleanup at once but check if we have time\n const hasTime =\n !deadline || deadline.timeRemaining() > 0 || deadline.didTimeout\n\n if (hasTime) {\n // Perform all cleanup operations except events\n this.sync.cleanup()\n this.state.cleanup()\n this.changes.cleanup()\n this.indexes.cleanup()\n\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n this.gcTimeoutId = null\n }\n\n this.hasBeenReady = false\n\n // Call any pending onFirstReady callbacks before clearing them.\n // This ensures preload() promises resolve during cleanup instead of hanging.\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => {\n try {\n callback()\n } catch (error) {\n console.error(\n `${this.config.id ? `[${this.config.id}] ` : ``}Error in onFirstReady callback during cleanup:`,\n error,\n )\n }\n })\n\n // Set status to cleaned-up after everything is cleaned up\n // This fires the status:change event to notify listeners\n this.setStatus(`cleaned-up`)\n\n // Finally, cleanup event handlers after the event has been fired\n this.events.cleanup()\n\n return true\n } else {\n // If we don't have time, reschedule for the next idle period\n this.scheduleIdleCleanup()\n return false\n }\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n */\n public onFirstReady(callback: () => void): void {\n // If already ready, call immediately\n if (this.hasBeenReady) {\n callback()\n return\n }\n\n this.onFirstReadyCallbacks.push(callback)\n }\n\n public cleanup(): void {\n // Cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n\n // Perform cleanup immediately (used when explicitly called)\n this.performCleanup()\n }\n}\n"],"names":["InvalidCollectionStatusTransitionError","CollectionStateError","CollectionInErrorStateError","safeCancelIdleCallback","safeRequestIdleCallback"],"mappings":"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"}
1
+ {"version":3,"file":"lifecycle.cjs","sources":["../../../src/collection/lifecycle.ts"],"sourcesContent":["import {\n CollectionInErrorStateError,\n CollectionStateError,\n InvalidCollectionStatusTransitionError,\n} from '../errors'\nimport {\n safeCancelIdleCallback,\n safeRequestIdleCallback,\n} from '../utils/browser-polyfills'\nimport { CleanupQueue } from './cleanup-queue'\nimport type { IdleCallbackDeadline } from '../utils/browser-polyfills'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { CollectionConfig, CollectionStatus } from '../types'\nimport type { CollectionEventsManager } from './events'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionSyncManager } from './sync'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionLifecycleManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private config: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public status: CollectionStatus = `idle`\n public hasBeenReady = false\n public hasReceivedFirstCommit = false\n public onFirstReadyCallbacks: Array<() => void> = []\n private idleCallbackId: number | null = null\n\n /**\n * Creates a new CollectionLifecycleManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n }\n\n setDeps(deps: {\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.indexes = deps.indexes\n this.events = deps.events\n this.changes = deps.changes\n this.sync = deps.sync\n this.state = deps.state\n }\n\n /**\n * Validates state transitions to prevent invalid status changes\n */\n public validateStatusTransition(\n from: CollectionStatus,\n to: CollectionStatus,\n ): void {\n if (from === to) {\n // Allow same state transitions\n return\n }\n const validTransitions: Record<\n CollectionStatus,\n Array<CollectionStatus>\n > = {\n idle: [`loading`, `error`, `cleaned-up`],\n loading: [`ready`, `error`, `cleaned-up`],\n ready: [`cleaned-up`, `error`],\n error: [`cleaned-up`, `idle`],\n 'cleaned-up': [`loading`, `error`],\n }\n\n if (!validTransitions[from].includes(to)) {\n throw new InvalidCollectionStatusTransitionError(from, to, this.id)\n }\n }\n\n /**\n * Safely update the collection status with validation\n * @private\n */\n public setStatus(\n newStatus: CollectionStatus,\n allowReady: boolean = false,\n ): void {\n if (newStatus === `ready` && !allowReady) {\n // setStatus('ready') is an internal method that should not be called directly\n // Instead, use markReady to transition to ready triggering the necessary events\n // and side effects.\n throw new CollectionStateError(\n `You can't directly call \"setStatus('ready'). You must use markReady instead.`,\n )\n }\n this.validateStatusTransition(this.status, newStatus)\n const previousStatus = this.status\n this.status = newStatus\n\n // Emit event\n this.events.emitStatusChange(newStatus, previousStatus)\n }\n\n /**\n * Validates that the collection is in a usable state for data operations\n * @private\n */\n public validateCollectionUsable(operation: string): void {\n switch (this.status) {\n case `error`:\n throw new CollectionInErrorStateError(operation, this.id)\n case `cleaned-up`:\n // Automatically restart the collection when operations are called on cleaned-up collections\n this.sync.startSync()\n break\n }\n }\n\n /**\n * Mark the collection as ready for use\n * This is called by sync implementations to explicitly signal that the collection is ready,\n * providing a more intuitive alternative to using commits for readiness signaling\n * @private - Should only be called by sync implementations\n */\n public markReady(): void {\n this.validateStatusTransition(this.status, `ready`)\n // Can transition to ready from loading state\n if (this.status === `loading`) {\n this.setStatus(`ready`, true)\n\n // Call any registered first ready callbacks (only on first time becoming ready)\n if (!this.hasBeenReady) {\n this.hasBeenReady = true\n\n // Also mark as having received first commit for backwards compatibility\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n // Notify dependents when markReady is called, after status is set\n // This ensures live queries get notified when their dependencies become ready\n if (this.changes.changeSubscriptions.size > 0) {\n this.changes.emitEmptyReadyEvent()\n }\n }\n }\n\n /**\n * Start the garbage collection timer\n * Called when the collection becomes inactive (no subscribers)\n */\n public startGCTimer(): void {\n const gcTime = this.config.gcTime ?? 300000 // 5 minutes default\n\n // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.\n // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC,\n // so we must explicitly check for non-finite values here.\n if (gcTime <= 0 || !Number.isFinite(gcTime)) {\n return\n }\n\n CleanupQueue.getInstance().schedule(this, gcTime, () => {\n if (this.changes.activeSubscribersCount === 0) {\n // Schedule cleanup during idle time to avoid blocking the UI thread\n this.scheduleIdleCleanup()\n }\n })\n }\n\n /**\n * Cancel the garbage collection timer\n * Called when the collection becomes active again\n */\n public cancelGCTimer(): void {\n CleanupQueue.getInstance().cancel(this)\n // Also cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n }\n\n /**\n * Schedule cleanup to run during browser idle time\n * This prevents blocking the UI thread during cleanup operations\n */\n private scheduleIdleCleanup(): void {\n // Cancel any existing idle callback\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n }\n\n // Schedule cleanup with a timeout of 1 second\n // This ensures cleanup happens even if the browser is busy\n this.idleCallbackId = safeRequestIdleCallback(\n (deadline) => {\n // Perform cleanup if we still have no subscribers\n if (this.changes.activeSubscribersCount === 0) {\n const cleanupCompleted = this.performCleanup(deadline)\n // Only clear the callback ID if cleanup actually completed\n if (cleanupCompleted) {\n this.idleCallbackId = null\n }\n } else {\n // No need to cleanup, clear the callback ID\n this.idleCallbackId = null\n }\n },\n { timeout: 1000 },\n )\n }\n\n /**\n * Perform cleanup operations, optionally in chunks during idle time\n * @returns true if cleanup was completed, false if it was rescheduled\n */\n private performCleanup(deadline?: IdleCallbackDeadline): boolean {\n // If we have a deadline, we can potentially split cleanup into chunks\n // For now, we'll do all cleanup at once but check if we have time\n const hasTime =\n !deadline || deadline.timeRemaining() > 0 || deadline.didTimeout\n\n if (hasTime) {\n // Perform all cleanup operations except events\n this.sync.cleanup()\n this.state.cleanup()\n this.changes.cleanup()\n this.indexes.cleanup()\n\n CleanupQueue.getInstance().cancel(this)\n\n this.hasBeenReady = false\n\n // Call any pending onFirstReady callbacks before clearing them.\n // This ensures preload() promises resolve during cleanup instead of hanging.\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => {\n try {\n callback()\n } catch (error) {\n console.error(\n `${this.config.id ? `[${this.config.id}] ` : ``}Error in onFirstReady callback during cleanup:`,\n error,\n )\n }\n })\n\n // Set status to cleaned-up after everything is cleaned up\n // This fires the status:change event to notify listeners\n this.setStatus(`cleaned-up`)\n\n // Finally, cleanup event handlers after the event has been fired\n this.events.cleanup()\n\n return true\n } else {\n // If we don't have time, reschedule for the next idle period\n this.scheduleIdleCleanup()\n return false\n }\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n */\n public onFirstReady(callback: () => void): void {\n // If already ready, call immediately\n if (this.hasBeenReady) {\n callback()\n return\n }\n\n this.onFirstReadyCallbacks.push(callback)\n }\n\n public cleanup(): void {\n // Cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n\n // Perform cleanup immediately (used when explicitly called)\n this.performCleanup()\n }\n}\n"],"names":["InvalidCollectionStatusTransitionError","CollectionStateError","CollectionInErrorStateError","CleanupQueue","safeCancelIdleCallback","safeRequestIdleCallback"],"mappings":";;;;;AAmBO,MAAM,2BAKX;AAAA;AAAA;AAAA;AAAA,EAkBA,YAAY,QAAkD,IAAY;AAT1E,SAAO,SAA2B;AAClC,SAAO,eAAe;AACtB,SAAO,yBAAyB;AAChC,SAAO,wBAA2C,CAAA;AAClD,SAAQ,iBAAgC;AAMtC,SAAK,SAAS;AACd,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,QAAQ,MAML;AACD,SAAK,UAAU,KAAK;AACpB,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AACpB,SAAK,OAAO,KAAK;AACjB,SAAK,QAAQ,KAAK;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKO,yBACL,MACA,IACM;AACN,QAAI,SAAS,IAAI;AAEf;AAAA,IACF;AACA,UAAM,mBAGF;AAAA,MACF,MAAM,CAAC,WAAW,SAAS,YAAY;AAAA,MACvC,SAAS,CAAC,SAAS,SAAS,YAAY;AAAA,MACxC,OAAO,CAAC,cAAc,OAAO;AAAA,MAC7B,OAAO,CAAC,cAAc,MAAM;AAAA,MAC5B,cAAc,CAAC,WAAW,OAAO;AAAA,IAAA;AAGnC,QAAI,CAAC,iBAAiB,IAAI,EAAE,SAAS,EAAE,GAAG;AACxC,YAAM,IAAIA,OAAAA,uCAAuC,MAAM,IAAI,KAAK,EAAE;AAAA,IACpE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,UACL,WACA,aAAsB,OAChB;AACN,QAAI,cAAc,WAAW,CAAC,YAAY;AAIxC,YAAM,IAAIC,OAAAA;AAAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AACA,SAAK,yBAAyB,KAAK,QAAQ,SAAS;AACpD,UAAM,iBAAiB,KAAK;AAC5B,SAAK,SAAS;AAGd,SAAK,OAAO,iBAAiB,WAAW,cAAc;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,yBAAyB,WAAyB;AACvD,YAAQ,KAAK,QAAA;AAAA,MACX,KAAK;AACH,cAAM,IAAIC,OAAAA,4BAA4B,WAAW,KAAK,EAAE;AAAA,MAC1D,KAAK;AAEH,aAAK,KAAK,UAAA;AACV;AAAA,IAAA;AAAA,EAEN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,YAAkB;AACvB,SAAK,yBAAyB,KAAK,QAAQ,OAAO;AAElD,QAAI,KAAK,WAAW,WAAW;AAC7B,WAAK,UAAU,SAAS,IAAI;AAG5B,UAAI,CAAC,KAAK,cAAc;AACtB,aAAK,eAAe;AAGpB,YAAI,CAAC,KAAK,wBAAwB;AAChC,eAAK,yBAAyB;AAAA,QAChC;AAEA,cAAM,YAAY,CAAC,GAAG,KAAK,qBAAqB;AAChD,aAAK,wBAAwB,CAAA;AAC7B,kBAAU,QAAQ,CAAC,aAAa,SAAA,CAAU;AAAA,MAC5C;AAGA,UAAI,KAAK,QAAQ,oBAAoB,OAAO,GAAG;AAC7C,aAAK,QAAQ,oBAAA;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,eAAqB;AAC1B,UAAM,SAAS,KAAK,OAAO,UAAU;AAKrC,QAAI,UAAU,KAAK,CAAC,OAAO,SAAS,MAAM,GAAG;AAC3C;AAAA,IACF;AAEAC,iBAAAA,aAAa,YAAA,EAAc,SAAS,MAAM,QAAQ,MAAM;AACtD,UAAI,KAAK,QAAQ,2BAA2B,GAAG;AAE7C,aAAK,oBAAA;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,gBAAsB;AAC3BA,iBAAAA,aAAa,YAAA,EAAc,OAAO,IAAI;AAEtC,QAAI,KAAK,mBAAmB,MAAM;AAChCC,uBAAAA,uBAAuB,KAAK,cAAc;AAC1C,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA4B;AAElC,QAAI,KAAK,mBAAmB,MAAM;AAChCA,uBAAAA,uBAAuB,KAAK,cAAc;AAAA,IAC5C;AAIA,SAAK,iBAAiBC,iBAAAA;AAAAA,MACpB,CAAC,aAAa;AAEZ,YAAI,KAAK,QAAQ,2BAA2B,GAAG;AAC7C,gBAAM,mBAAmB,KAAK,eAAe,QAAQ;AAErD,cAAI,kBAAkB;AACpB,iBAAK,iBAAiB;AAAA,UACxB;AAAA,QACF,OAAO;AAEL,eAAK,iBAAiB;AAAA,QACxB;AAAA,MACF;AAAA,MACA,EAAE,SAAS,IAAA;AAAA,IAAK;AAAA,EAEpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,UAA0C;AAG/D,UAAM,UACJ,CAAC,YAAY,SAAS,kBAAkB,KAAK,SAAS;AAExD,QAAI,SAAS;AAEX,WAAK,KAAK,QAAA;AACV,WAAK,MAAM,QAAA;AACX,WAAK,QAAQ,QAAA;AACb,WAAK,QAAQ,QAAA;AAEbF,mBAAAA,aAAa,YAAA,EAAc,OAAO,IAAI;AAEtC,WAAK,eAAe;AAIpB,YAAM,YAAY,CAAC,GAAG,KAAK,qBAAqB;AAChD,WAAK,wBAAwB,CAAA;AAC7B,gBAAU,QAAQ,CAAC,aAAa;AAC9B,YAAI;AACF,mBAAA;AAAA,QACF,SAAS,OAAO;AACd,kBAAQ;AAAA,YACN,GAAG,KAAK,OAAO,KAAK,IAAI,KAAK,OAAO,EAAE,OAAO,EAAE;AAAA,YAC/C;AAAA,UAAA;AAAA,QAEJ;AAAA,MACF,CAAC;AAID,WAAK,UAAU,YAAY;AAG3B,WAAK,OAAO,QAAA;AAEZ,aAAO;AAAA,IACT,OAAO;AAEL,WAAK,oBAAA;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,aAAa,UAA4B;AAE9C,QAAI,KAAK,cAAc;AACrB,eAAA;AACA;AAAA,IACF;AAEA,SAAK,sBAAsB,KAAK,QAAQ;AAAA,EAC1C;AAAA,EAEO,UAAgB;AAErB,QAAI,KAAK,mBAAmB,MAAM;AAChCC,uBAAAA,uBAAuB,KAAK,cAAc;AAC1C,WAAK,iBAAiB;AAAA,IACxB;AAGA,SAAK,eAAA;AAAA,EACP;AACF;;"}
@@ -17,7 +17,6 @@ export declare class CollectionLifecycleManager<TOutput extends object = Record<
17
17
  hasBeenReady: boolean;
18
18
  hasReceivedFirstCommit: boolean;
19
19
  onFirstReadyCallbacks: Array<() => void>;
20
- gcTimeoutId: ReturnType<typeof setTimeout> | null;
21
20
  private idleCallbackId;
22
21
  /**
23
22
  * Creates a new CollectionLifecycleManager instance