@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.
- package/dist/cjs/{change-events.cjs → collection/change-events.cjs} +13 -42
- package/dist/cjs/collection/change-events.cjs.map +1 -0
- package/dist/{esm/change-events.d.ts → cjs/collection/change-events.d.cts} +6 -6
- package/dist/cjs/collection/changes.cjs +108 -0
- package/dist/cjs/collection/changes.cjs.map +1 -0
- package/dist/cjs/collection/changes.d.cts +53 -0
- package/dist/cjs/{collection-events.cjs → collection/events.cjs} +7 -5
- package/dist/cjs/collection/events.cjs.map +1 -0
- package/dist/cjs/{collection-events.d.cts → collection/events.d.cts} +7 -4
- package/dist/cjs/collection/index.cjs +417 -0
- package/dist/cjs/collection/index.cjs.map +1 -0
- package/dist/{esm/collection.d.ts → cjs/collection/index.d.cts} +46 -184
- package/dist/cjs/collection/indexes.cjs +124 -0
- package/dist/cjs/collection/indexes.cjs.map +1 -0
- package/dist/cjs/collection/indexes.d.cts +47 -0
- package/dist/cjs/collection/lifecycle.cjs +150 -0
- package/dist/cjs/collection/lifecycle.cjs.map +1 -0
- package/dist/cjs/collection/lifecycle.d.cts +70 -0
- package/dist/cjs/collection/mutations.cjs +315 -0
- package/dist/cjs/collection/mutations.cjs.map +1 -0
- package/dist/cjs/collection/mutations.d.cts +33 -0
- package/dist/cjs/collection/state.cjs +597 -0
- package/dist/cjs/collection/state.cjs.map +1 -0
- package/dist/cjs/collection/state.d.cts +122 -0
- package/dist/cjs/collection/subscription.cjs +160 -0
- package/dist/cjs/collection/subscription.cjs.map +1 -0
- package/dist/cjs/collection/subscription.d.cts +57 -0
- package/dist/cjs/collection/sync.cjs +154 -0
- package/dist/cjs/collection/sync.cjs.map +1 -0
- package/dist/cjs/collection/sync.d.cts +34 -0
- package/dist/cjs/index.cjs +8 -8
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.d.cts +1 -1
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +2 -2
- package/dist/cjs/indexes/btree-index.cjs +2 -2
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +1 -1
- package/dist/cjs/query/builder/index.cjs +2 -2
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/types.d.cts +1 -1
- package/dist/cjs/query/compiler/index.cjs +5 -2
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +3 -2
- package/dist/cjs/query/compiler/joins.cjs +22 -24
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +3 -2
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +29 -12
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +3 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +43 -104
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +4 -7
- package/dist/cjs/query/live-query-collection.cjs +2 -2
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +1 -1
- package/dist/cjs/transactions.cjs +3 -3
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +12 -10
- package/dist/{cjs/change-events.d.cts → esm/collection/change-events.d.ts} +6 -6
- package/dist/esm/{change-events.js → collection/change-events.js} +13 -42
- package/dist/esm/collection/change-events.js.map +1 -0
- package/dist/esm/collection/changes.d.ts +53 -0
- package/dist/esm/collection/changes.js +108 -0
- package/dist/esm/collection/changes.js.map +1 -0
- package/dist/esm/{collection-events.d.ts → collection/events.d.ts} +7 -4
- package/dist/esm/{collection-events.js → collection/events.js} +7 -5
- package/dist/esm/collection/events.js.map +1 -0
- package/dist/{cjs/collection.d.cts → esm/collection/index.d.ts} +46 -184
- package/dist/esm/collection/index.js +417 -0
- package/dist/esm/collection/index.js.map +1 -0
- package/dist/esm/collection/indexes.d.ts +47 -0
- package/dist/esm/collection/indexes.js +124 -0
- package/dist/esm/collection/indexes.js.map +1 -0
- package/dist/esm/collection/lifecycle.d.ts +70 -0
- package/dist/esm/collection/lifecycle.js +150 -0
- package/dist/esm/collection/lifecycle.js.map +1 -0
- package/dist/esm/collection/mutations.d.ts +33 -0
- package/dist/esm/collection/mutations.js +315 -0
- package/dist/esm/collection/mutations.js.map +1 -0
- package/dist/esm/collection/state.d.ts +122 -0
- package/dist/esm/collection/state.js +597 -0
- package/dist/esm/collection/state.js.map +1 -0
- package/dist/esm/collection/subscription.d.ts +57 -0
- package/dist/esm/collection/subscription.js +160 -0
- package/dist/esm/collection/subscription.js.map +1 -0
- package/dist/esm/collection/sync.d.ts +34 -0
- package/dist/esm/collection/sync.js +154 -0
- package/dist/esm/collection/sync.js.map +1 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/indexes/auto-index.d.ts +1 -1
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +2 -2
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.d.ts +1 -1
- package/dist/esm/indexes/btree-index.js +2 -2
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/proxy.js +1 -1
- package/dist/esm/query/builder/index.js +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +1 -1
- package/dist/esm/query/compiler/index.d.ts +3 -2
- package/dist/esm/query/compiler/index.js +5 -2
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +3 -2
- package/dist/esm/query/compiler/joins.js +22 -24
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -1
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/ir.d.ts +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +3 -0
- package/dist/esm/query/live/collection-config-builder.js +29 -12
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +4 -7
- package/dist/esm/query/live/collection-subscriber.js +43 -104
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +1 -1
- package/dist/esm/query/live-query-collection.js +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/transactions.js +3 -3
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +12 -10
- package/package.json +2 -2
- package/src/{change-events.ts → collection/change-events.ts} +25 -39
- package/src/collection/changes.ts +163 -0
- package/src/{collection-events.ts → collection/events.ts} +8 -6
- package/src/collection/index.ts +808 -0
- package/src/collection/indexes.ts +172 -0
- package/src/collection/lifecycle.ts +221 -0
- package/src/collection/mutations.ts +535 -0
- package/src/collection/state.ts +866 -0
- package/src/collection/subscription.ts +239 -0
- package/src/collection/sync.ts +235 -0
- package/src/index.ts +2 -2
- package/src/indexes/auto-index.ts +1 -1
- package/src/indexes/base-index.ts +3 -3
- package/src/indexes/btree-index.ts +2 -2
- package/src/query/builder/index.ts +1 -1
- package/src/query/builder/types.ts +1 -1
- package/src/query/compiler/index.ts +7 -1
- package/src/query/compiler/joins.ts +28 -41
- package/src/query/compiler/order-by.ts +1 -1
- package/src/query/ir.ts +1 -1
- package/src/query/live/collection-config-builder.ts +48 -22
- package/src/query/live/collection-subscriber.ts +63 -168
- package/src/query/live-query-collection.ts +2 -2
- package/src/transactions.ts +3 -3
- package/src/types.ts +14 -15
- package/dist/cjs/change-events.cjs.map +0 -1
- package/dist/cjs/collection-events.cjs.map +0 -1
- package/dist/cjs/collection.cjs +0 -1625
- package/dist/cjs/collection.cjs.map +0 -1
- package/dist/esm/change-events.js.map +0 -1
- package/dist/esm/collection-events.js.map +0 -1
- package/dist/esm/collection.js +0 -1625
- package/dist/esm/collection.js.map +0 -1
- 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
|
+
}
|