@tanstack/db 0.3.2 → 0.4.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 (164) hide show
  1. package/dist/cjs/{change-events.cjs → collection/change-events.cjs} +13 -42
  2. package/dist/cjs/collection/change-events.cjs.map +1 -0
  3. package/dist/{esm/change-events.d.ts → cjs/collection/change-events.d.cts} +6 -6
  4. package/dist/cjs/collection/changes.cjs +108 -0
  5. package/dist/cjs/collection/changes.cjs.map +1 -0
  6. package/dist/cjs/collection/changes.d.cts +53 -0
  7. package/dist/cjs/{collection-events.cjs → collection/events.cjs} +7 -5
  8. package/dist/cjs/collection/events.cjs.map +1 -0
  9. package/dist/cjs/{collection-events.d.cts → collection/events.d.cts} +7 -4
  10. package/dist/cjs/collection/index.cjs +417 -0
  11. package/dist/cjs/collection/index.cjs.map +1 -0
  12. package/dist/{esm/collection.d.ts → cjs/collection/index.d.cts} +46 -184
  13. package/dist/cjs/collection/indexes.cjs +124 -0
  14. package/dist/cjs/collection/indexes.cjs.map +1 -0
  15. package/dist/cjs/collection/indexes.d.cts +47 -0
  16. package/dist/cjs/collection/lifecycle.cjs +150 -0
  17. package/dist/cjs/collection/lifecycle.cjs.map +1 -0
  18. package/dist/cjs/collection/lifecycle.d.cts +70 -0
  19. package/dist/cjs/collection/mutations.cjs +315 -0
  20. package/dist/cjs/collection/mutations.cjs.map +1 -0
  21. package/dist/cjs/collection/mutations.d.cts +33 -0
  22. package/dist/cjs/collection/state.cjs +597 -0
  23. package/dist/cjs/collection/state.cjs.map +1 -0
  24. package/dist/cjs/collection/state.d.cts +122 -0
  25. package/dist/cjs/collection/subscription.cjs +160 -0
  26. package/dist/cjs/collection/subscription.cjs.map +1 -0
  27. package/dist/cjs/collection/subscription.d.cts +57 -0
  28. package/dist/cjs/collection/sync.cjs +154 -0
  29. package/dist/cjs/collection/sync.cjs.map +1 -0
  30. package/dist/cjs/collection/sync.d.cts +34 -0
  31. package/dist/cjs/index.cjs +8 -8
  32. package/dist/cjs/index.d.cts +2 -2
  33. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  34. package/dist/cjs/indexes/auto-index.d.cts +1 -1
  35. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  36. package/dist/cjs/indexes/base-index.d.cts +2 -2
  37. package/dist/cjs/indexes/btree-index.cjs +2 -2
  38. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  39. package/dist/cjs/indexes/btree-index.d.cts +1 -1
  40. package/dist/cjs/query/builder/index.cjs +2 -2
  41. package/dist/cjs/query/builder/index.cjs.map +1 -1
  42. package/dist/cjs/query/builder/types.d.cts +1 -1
  43. package/dist/cjs/query/compiler/index.cjs +5 -2
  44. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  45. package/dist/cjs/query/compiler/index.d.cts +3 -2
  46. package/dist/cjs/query/compiler/joins.cjs +22 -24
  47. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  48. package/dist/cjs/query/compiler/joins.d.cts +3 -2
  49. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  50. package/dist/cjs/query/compiler/order-by.d.cts +1 -1
  51. package/dist/cjs/query/ir.cjs.map +1 -1
  52. package/dist/cjs/query/ir.d.cts +1 -1
  53. package/dist/cjs/query/live/collection-config-builder.cjs +29 -12
  54. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  55. package/dist/cjs/query/live/collection-config-builder.d.cts +3 -0
  56. package/dist/cjs/query/live/collection-subscriber.cjs +43 -104
  57. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  58. package/dist/cjs/query/live/collection-subscriber.d.cts +4 -7
  59. package/dist/cjs/query/live-query-collection.cjs +2 -2
  60. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  61. package/dist/cjs/query/live-query-collection.d.cts +1 -1
  62. package/dist/cjs/transactions.cjs +3 -3
  63. package/dist/cjs/transactions.cjs.map +1 -1
  64. package/dist/cjs/types.d.cts +12 -10
  65. package/dist/{cjs/change-events.d.cts → esm/collection/change-events.d.ts} +6 -6
  66. package/dist/esm/{change-events.js → collection/change-events.js} +13 -42
  67. package/dist/esm/collection/change-events.js.map +1 -0
  68. package/dist/esm/collection/changes.d.ts +53 -0
  69. package/dist/esm/collection/changes.js +108 -0
  70. package/dist/esm/collection/changes.js.map +1 -0
  71. package/dist/esm/{collection-events.d.ts → collection/events.d.ts} +7 -4
  72. package/dist/esm/{collection-events.js → collection/events.js} +7 -5
  73. package/dist/esm/collection/events.js.map +1 -0
  74. package/dist/{cjs/collection.d.cts → esm/collection/index.d.ts} +46 -184
  75. package/dist/esm/collection/index.js +417 -0
  76. package/dist/esm/collection/index.js.map +1 -0
  77. package/dist/esm/collection/indexes.d.ts +47 -0
  78. package/dist/esm/collection/indexes.js +124 -0
  79. package/dist/esm/collection/indexes.js.map +1 -0
  80. package/dist/esm/collection/lifecycle.d.ts +70 -0
  81. package/dist/esm/collection/lifecycle.js +150 -0
  82. package/dist/esm/collection/lifecycle.js.map +1 -0
  83. package/dist/esm/collection/mutations.d.ts +33 -0
  84. package/dist/esm/collection/mutations.js +315 -0
  85. package/dist/esm/collection/mutations.js.map +1 -0
  86. package/dist/esm/collection/state.d.ts +122 -0
  87. package/dist/esm/collection/state.js +597 -0
  88. package/dist/esm/collection/state.js.map +1 -0
  89. package/dist/esm/collection/subscription.d.ts +57 -0
  90. package/dist/esm/collection/subscription.js +160 -0
  91. package/dist/esm/collection/subscription.js.map +1 -0
  92. package/dist/esm/collection/sync.d.ts +34 -0
  93. package/dist/esm/collection/sync.js +154 -0
  94. package/dist/esm/collection/sync.js.map +1 -0
  95. package/dist/esm/index.d.ts +2 -2
  96. package/dist/esm/index.js +1 -1
  97. package/dist/esm/indexes/auto-index.d.ts +1 -1
  98. package/dist/esm/indexes/auto-index.js.map +1 -1
  99. package/dist/esm/indexes/base-index.d.ts +2 -2
  100. package/dist/esm/indexes/base-index.js.map +1 -1
  101. package/dist/esm/indexes/btree-index.d.ts +1 -1
  102. package/dist/esm/indexes/btree-index.js +2 -2
  103. package/dist/esm/indexes/btree-index.js.map +1 -1
  104. package/dist/esm/proxy.js +1 -1
  105. package/dist/esm/query/builder/index.js +1 -1
  106. package/dist/esm/query/builder/index.js.map +1 -1
  107. package/dist/esm/query/builder/types.d.ts +1 -1
  108. package/dist/esm/query/compiler/index.d.ts +3 -2
  109. package/dist/esm/query/compiler/index.js +5 -2
  110. package/dist/esm/query/compiler/index.js.map +1 -1
  111. package/dist/esm/query/compiler/joins.d.ts +3 -2
  112. package/dist/esm/query/compiler/joins.js +22 -24
  113. package/dist/esm/query/compiler/joins.js.map +1 -1
  114. package/dist/esm/query/compiler/order-by.d.ts +1 -1
  115. package/dist/esm/query/compiler/order-by.js.map +1 -1
  116. package/dist/esm/query/ir.d.ts +1 -1
  117. package/dist/esm/query/ir.js.map +1 -1
  118. package/dist/esm/query/live/collection-config-builder.d.ts +3 -0
  119. package/dist/esm/query/live/collection-config-builder.js +29 -12
  120. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  121. package/dist/esm/query/live/collection-subscriber.d.ts +4 -7
  122. package/dist/esm/query/live/collection-subscriber.js +43 -104
  123. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  124. package/dist/esm/query/live-query-collection.d.ts +1 -1
  125. package/dist/esm/query/live-query-collection.js +1 -1
  126. package/dist/esm/query/live-query-collection.js.map +1 -1
  127. package/dist/esm/transactions.js +3 -3
  128. package/dist/esm/transactions.js.map +1 -1
  129. package/dist/esm/types.d.ts +12 -10
  130. package/package.json +2 -2
  131. package/src/{change-events.ts → collection/change-events.ts} +25 -39
  132. package/src/collection/changes.ts +163 -0
  133. package/src/{collection-events.ts → collection/events.ts} +8 -6
  134. package/src/collection/index.ts +808 -0
  135. package/src/collection/indexes.ts +172 -0
  136. package/src/collection/lifecycle.ts +221 -0
  137. package/src/collection/mutations.ts +535 -0
  138. package/src/collection/state.ts +866 -0
  139. package/src/collection/subscription.ts +239 -0
  140. package/src/collection/sync.ts +235 -0
  141. package/src/index.ts +2 -2
  142. package/src/indexes/auto-index.ts +1 -1
  143. package/src/indexes/base-index.ts +3 -3
  144. package/src/indexes/btree-index.ts +2 -2
  145. package/src/query/builder/index.ts +1 -1
  146. package/src/query/builder/types.ts +1 -1
  147. package/src/query/compiler/index.ts +7 -1
  148. package/src/query/compiler/joins.ts +28 -41
  149. package/src/query/compiler/order-by.ts +1 -1
  150. package/src/query/ir.ts +1 -1
  151. package/src/query/live/collection-config-builder.ts +48 -22
  152. package/src/query/live/collection-subscriber.ts +63 -168
  153. package/src/query/live-query-collection.ts +2 -2
  154. package/src/transactions.ts +3 -3
  155. package/src/types.ts +14 -15
  156. package/dist/cjs/change-events.cjs.map +0 -1
  157. package/dist/cjs/collection-events.cjs.map +0 -1
  158. package/dist/cjs/collection.cjs +0 -1625
  159. package/dist/cjs/collection.cjs.map +0 -1
  160. package/dist/esm/change-events.js.map +0 -1
  161. package/dist/esm/collection-events.js.map +0 -1
  162. package/dist/esm/collection.js +0 -1625
  163. package/dist/esm/collection.js.map +0 -1
  164. package/src/collection.ts +0 -2564
@@ -0,0 +1,172 @@
1
+ import { IndexProxy, LazyIndexWrapper } from "../indexes/lazy-index"
2
+ import {
3
+ createSingleRowRefProxy,
4
+ toExpression,
5
+ } from "../query/builder/ref-proxy"
6
+ import { BTreeIndex } from "../indexes/btree-index"
7
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
8
+ import type { BaseIndex, IndexResolver } from "../indexes/base-index"
9
+ import type { ChangeMessage } from "../types"
10
+ import type { IndexOptions } from "../indexes/index-options"
11
+ import type { SingleRowRefProxy } from "../query/builder/ref-proxy"
12
+ import type { CollectionLifecycleManager } from "./lifecycle"
13
+ import type { CollectionStateManager } from "./state"
14
+
15
+ export class CollectionIndexesManager<
16
+ TOutput extends object = Record<string, unknown>,
17
+ TKey extends string | number = string | number,
18
+ TSchema extends StandardSchemaV1 = StandardSchemaV1,
19
+ TInput extends object = TOutput,
20
+ > {
21
+ private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>
22
+ private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>
23
+
24
+ public lazyIndexes = new Map<number, LazyIndexWrapper<TKey>>()
25
+ public resolvedIndexes = new Map<number, BaseIndex<TKey>>()
26
+ public isIndexesResolved = false
27
+ public indexCounter = 0
28
+
29
+ constructor() {}
30
+
31
+ setDeps(deps: {
32
+ state: CollectionStateManager<TOutput, TKey, TSchema, TInput>
33
+ lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>
34
+ }) {
35
+ this.state = deps.state
36
+ this.lifecycle = deps.lifecycle
37
+ }
38
+
39
+ /**
40
+ * Creates an index on a collection for faster queries.
41
+ */
42
+ public createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(
43
+ indexCallback: (row: SingleRowRefProxy<TOutput>) => any,
44
+ config: IndexOptions<TResolver> = {}
45
+ ): IndexProxy<TKey> {
46
+ this.lifecycle.validateCollectionUsable(`createIndex`)
47
+
48
+ const indexId = ++this.indexCounter
49
+ const singleRowRefProxy = createSingleRowRefProxy<TOutput>()
50
+ const indexExpression = indexCallback(singleRowRefProxy)
51
+ const expression = toExpression(indexExpression)
52
+
53
+ // Default to BTreeIndex if no type specified
54
+ const resolver = config.indexType ?? (BTreeIndex as unknown as TResolver)
55
+
56
+ // Create lazy wrapper
57
+ const lazyIndex = new LazyIndexWrapper<TKey>(
58
+ indexId,
59
+ expression,
60
+ config.name,
61
+ resolver,
62
+ config.options,
63
+ this.state.entries()
64
+ )
65
+
66
+ this.lazyIndexes.set(indexId, lazyIndex)
67
+
68
+ // For BTreeIndex, resolve immediately and synchronously
69
+ if ((resolver as unknown) === BTreeIndex) {
70
+ try {
71
+ const resolvedIndex = lazyIndex.getResolved()
72
+ this.resolvedIndexes.set(indexId, resolvedIndex)
73
+ } catch (error) {
74
+ console.warn(`Failed to resolve BTreeIndex:`, error)
75
+ }
76
+ } else if (typeof resolver === `function` && resolver.prototype) {
77
+ // Other synchronous constructors - resolve immediately
78
+ try {
79
+ const resolvedIndex = lazyIndex.getResolved()
80
+ this.resolvedIndexes.set(indexId, resolvedIndex)
81
+ } catch {
82
+ // Fallback to async resolution
83
+ this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {
84
+ console.warn(`Failed to resolve single index:`, error)
85
+ })
86
+ }
87
+ } else if (this.isIndexesResolved) {
88
+ // Async loader but indexes are already resolved - resolve this one
89
+ this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {
90
+ console.warn(`Failed to resolve single index:`, error)
91
+ })
92
+ }
93
+
94
+ return new IndexProxy(indexId, lazyIndex)
95
+ }
96
+
97
+ /**
98
+ * Resolve all lazy indexes (called when collection first syncs)
99
+ */
100
+ public async resolveAllIndexes(): Promise<void> {
101
+ if (this.isIndexesResolved) return
102
+
103
+ const resolutionPromises = Array.from(this.lazyIndexes.entries()).map(
104
+ async ([indexId, lazyIndex]) => {
105
+ const resolvedIndex = await lazyIndex.resolve()
106
+
107
+ // Build index with current data
108
+ resolvedIndex.build(this.state.entries())
109
+
110
+ this.resolvedIndexes.set(indexId, resolvedIndex)
111
+ return { indexId, resolvedIndex }
112
+ }
113
+ )
114
+
115
+ await Promise.all(resolutionPromises)
116
+ this.isIndexesResolved = true
117
+ }
118
+
119
+ /**
120
+ * Resolve a single index immediately
121
+ */
122
+ private async resolveSingleIndex(
123
+ indexId: number,
124
+ lazyIndex: LazyIndexWrapper<TKey>
125
+ ): Promise<BaseIndex<TKey>> {
126
+ const resolvedIndex = await lazyIndex.resolve()
127
+ resolvedIndex.build(this.state.entries())
128
+ this.resolvedIndexes.set(indexId, resolvedIndex)
129
+ return resolvedIndex
130
+ }
131
+
132
+ /**
133
+ * Get resolved indexes for query optimization
134
+ */
135
+ get indexes(): Map<number, BaseIndex<TKey>> {
136
+ return this.resolvedIndexes
137
+ }
138
+
139
+ /**
140
+ * Updates all indexes when the collection changes
141
+ */
142
+ public updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {
143
+ for (const index of this.resolvedIndexes.values()) {
144
+ for (const change of changes) {
145
+ switch (change.type) {
146
+ case `insert`:
147
+ index.add(change.key, change.value)
148
+ break
149
+ case `update`:
150
+ if (change.previousValue) {
151
+ index.update(change.key, change.previousValue, change.value)
152
+ } else {
153
+ index.add(change.key, change.value)
154
+ }
155
+ break
156
+ case `delete`:
157
+ index.remove(change.key, change.value)
158
+ break
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Clean up the collection by stopping sync and clearing data
166
+ * This can be called manually or automatically by garbage collection
167
+ */
168
+ public cleanup(): void {
169
+ this.lazyIndexes.clear()
170
+ this.resolvedIndexes.clear()
171
+ }
172
+ }
@@ -0,0 +1,221 @@
1
+ import {
2
+ CollectionInErrorStateError,
3
+ InvalidCollectionStatusTransitionError,
4
+ } from "../errors"
5
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
6
+ import type { CollectionConfig, CollectionStatus } from "../types"
7
+ import type { CollectionEventsManager } from "./events"
8
+ import type { CollectionIndexesManager } from "./indexes"
9
+ import type { CollectionChangesManager } from "./changes"
10
+ import type { CollectionSyncManager } from "./sync"
11
+ import type { CollectionStateManager } from "./state"
12
+
13
+ export class CollectionLifecycleManager<
14
+ TOutput extends object = Record<string, unknown>,
15
+ TKey extends string | number = string | number,
16
+ TSchema extends StandardSchemaV1 = StandardSchemaV1,
17
+ TInput extends object = TOutput,
18
+ > {
19
+ private config: CollectionConfig<TOutput, TKey, TSchema>
20
+ private id: string
21
+ private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>
22
+ private events!: CollectionEventsManager
23
+ private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>
24
+ private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>
25
+ private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>
26
+
27
+ public status: CollectionStatus = `idle`
28
+ public hasBeenReady = false
29
+ public hasReceivedFirstCommit = false
30
+ public onFirstReadyCallbacks: Array<() => void> = []
31
+ public gcTimeoutId: ReturnType<typeof setTimeout> | null = null
32
+
33
+ /**
34
+ * Creates a new CollectionLifecycleManager instance
35
+ */
36
+ constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {
37
+ this.config = config
38
+ this.id = id
39
+ }
40
+
41
+ setDeps(deps: {
42
+ indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>
43
+ events: CollectionEventsManager
44
+ changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>
45
+ sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>
46
+ state: CollectionStateManager<TOutput, TKey, TSchema, TInput>
47
+ }) {
48
+ this.indexes = deps.indexes
49
+ this.events = deps.events
50
+ this.changes = deps.changes
51
+ this.sync = deps.sync
52
+ this.state = deps.state
53
+ }
54
+
55
+ /**
56
+ * Validates state transitions to prevent invalid status changes
57
+ */
58
+ public validateStatusTransition(
59
+ from: CollectionStatus,
60
+ to: CollectionStatus
61
+ ): void {
62
+ if (from === to) {
63
+ // Allow same state transitions
64
+ return
65
+ }
66
+ const validTransitions: Record<
67
+ CollectionStatus,
68
+ Array<CollectionStatus>
69
+ > = {
70
+ idle: [`loading`, `error`, `cleaned-up`],
71
+ loading: [`initialCommit`, `ready`, `error`, `cleaned-up`],
72
+ initialCommit: [`ready`, `error`, `cleaned-up`],
73
+ ready: [`cleaned-up`, `error`],
74
+ error: [`cleaned-up`, `idle`],
75
+ "cleaned-up": [`loading`, `error`],
76
+ }
77
+
78
+ if (!validTransitions[from].includes(to)) {
79
+ throw new InvalidCollectionStatusTransitionError(from, to, this.id)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Safely update the collection status with validation
85
+ * @private
86
+ */
87
+ public setStatus(newStatus: CollectionStatus): void {
88
+ this.validateStatusTransition(this.status, newStatus)
89
+ const previousStatus = this.status
90
+ this.status = newStatus
91
+
92
+ // Resolve indexes when collection becomes ready
93
+ if (newStatus === `ready` && !this.indexes.isIndexesResolved) {
94
+ // Resolve indexes asynchronously without blocking
95
+ this.indexes.resolveAllIndexes().catch((error) => {
96
+ console.warn(`Failed to resolve indexes:`, error)
97
+ })
98
+ }
99
+
100
+ // Emit event
101
+ this.events.emitStatusChange(newStatus, previousStatus)
102
+ }
103
+
104
+ /**
105
+ * Validates that the collection is in a usable state for data operations
106
+ * @private
107
+ */
108
+ public validateCollectionUsable(operation: string): void {
109
+ switch (this.status) {
110
+ case `error`:
111
+ throw new CollectionInErrorStateError(operation, this.id)
112
+ case `cleaned-up`:
113
+ // Automatically restart the collection when operations are called on cleaned-up collections
114
+ this.sync.startSync()
115
+ break
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Mark the collection as ready for use
121
+ * This is called by sync implementations to explicitly signal that the collection is ready,
122
+ * providing a more intuitive alternative to using commits for readiness signaling
123
+ * @private - Should only be called by sync implementations
124
+ */
125
+ public markReady(): void {
126
+ // Can transition to ready from loading or initialCommit states
127
+ if (this.status === `loading` || this.status === `initialCommit`) {
128
+ this.setStatus(`ready`)
129
+
130
+ // Call any registered first ready callbacks (only on first time becoming ready)
131
+ if (!this.hasBeenReady) {
132
+ this.hasBeenReady = true
133
+
134
+ // Also mark as having received first commit for backwards compatibility
135
+ if (!this.hasReceivedFirstCommit) {
136
+ this.hasReceivedFirstCommit = true
137
+ }
138
+
139
+ const callbacks = [...this.onFirstReadyCallbacks]
140
+ this.onFirstReadyCallbacks = []
141
+ callbacks.forEach((callback) => callback())
142
+ }
143
+ }
144
+
145
+ // Always notify dependents when markReady is called, after status is set
146
+ // This ensures live queries get notified when their dependencies become ready
147
+ if (this.changes.changeSubscriptions.size > 0) {
148
+ this.changes.emitEmptyReadyEvent()
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Start the garbage collection timer
154
+ * Called when the collection becomes inactive (no subscribers)
155
+ */
156
+ public startGCTimer(): void {
157
+ if (this.gcTimeoutId) {
158
+ clearTimeout(this.gcTimeoutId)
159
+ }
160
+
161
+ const gcTime = this.config.gcTime ?? 300000 // 5 minutes default
162
+
163
+ // If gcTime is 0, GC is disabled
164
+ if (gcTime === 0) {
165
+ return
166
+ }
167
+
168
+ this.gcTimeoutId = setTimeout(() => {
169
+ if (this.changes.activeSubscribersCount === 0) {
170
+ // We call the main collection cleanup, not just the one for the
171
+ // lifecycle manager
172
+ this.cleanup()
173
+ }
174
+ }, gcTime)
175
+ }
176
+
177
+ /**
178
+ * Cancel the garbage collection timer
179
+ * Called when the collection becomes active again
180
+ */
181
+ public cancelGCTimer(): void {
182
+ if (this.gcTimeoutId) {
183
+ clearTimeout(this.gcTimeoutId)
184
+ this.gcTimeoutId = null
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Register a callback to be executed when the collection first becomes ready
190
+ * Useful for preloading collections
191
+ * @param callback Function to call when the collection first becomes ready
192
+ */
193
+ public onFirstReady(callback: () => void): void {
194
+ // If already ready, call immediately
195
+ if (this.hasBeenReady) {
196
+ callback()
197
+ return
198
+ }
199
+
200
+ this.onFirstReadyCallbacks.push(callback)
201
+ }
202
+
203
+ public cleanup(): void {
204
+ this.events.cleanup()
205
+ this.sync.cleanup()
206
+ this.state.cleanup()
207
+ this.changes.cleanup()
208
+ this.indexes.cleanup()
209
+
210
+ if (this.gcTimeoutId) {
211
+ clearTimeout(this.gcTimeoutId)
212
+ this.gcTimeoutId = null
213
+ }
214
+
215
+ this.hasBeenReady = false
216
+ this.onFirstReadyCallbacks = []
217
+
218
+ // Set status to cleaned-up
219
+ this.setStatus(`cleaned-up`)
220
+ }
221
+ }