@tanstack/angular-db 0.0.1
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/README.md +279 -0
- package/package.json +62 -0
- package/src/index.ts +194 -0
package/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# @tanstack/angular-db
|
|
2
|
+
|
|
3
|
+
Angular hooks for TanStack DB. See [TanStack/db](https://github.com/TanStack/db) for more details.
|
|
4
|
+
|
|
5
|
+
Installation
|
|
6
|
+
|
|
7
|
+
npm install @tanstack/angular-db @tanstack/db
|
|
8
|
+
|
|
9
|
+
Usage
|
|
10
|
+
|
|
11
|
+
Basic Setup
|
|
12
|
+
|
|
13
|
+
First, create a collection:
|
|
14
|
+
|
|
15
|
+
import { createCollection, localOnlyCollectionOptions } from "@tanstack/db"
|
|
16
|
+
|
|
17
|
+
interface Todo {
|
|
18
|
+
id: number
|
|
19
|
+
text: string
|
|
20
|
+
completed: boolean
|
|
21
|
+
projectID: number
|
|
22
|
+
created_at: Date
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const todosCollection = createCollection(
|
|
26
|
+
localOnlyCollectionOptions<Todo>({
|
|
27
|
+
getKey: (todo: Todo) => todo.id,
|
|
28
|
+
initialData: [
|
|
29
|
+
{
|
|
30
|
+
id: 1,
|
|
31
|
+
text: "Learn Angular",
|
|
32
|
+
completed: false,
|
|
33
|
+
projectID: 1,
|
|
34
|
+
created_at: new Date(),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
Using injectLiveQuery in Components
|
|
41
|
+
|
|
42
|
+
Direct Collection Usage
|
|
43
|
+
|
|
44
|
+
The simplest way to use injectLiveQuery is to pass a collection directly:
|
|
45
|
+
|
|
46
|
+
import { Component } from "@angular/core"
|
|
47
|
+
import { injectLiveQuery } from "@tanstack/angular-db"
|
|
48
|
+
import { todosCollection } from "./collections/todos-collection"
|
|
49
|
+
|
|
50
|
+
@Component({
|
|
51
|
+
selector: "app-all-todos",
|
|
52
|
+
template: `
|
|
53
|
+
@if (allTodos.isReady()) {
|
|
54
|
+
<div>Total todos: {{ allTodos.data().length }}</div>
|
|
55
|
+
@for (todo of allTodos.data(); track todo.id) {
|
|
56
|
+
<div>{{ todo.text }}</div>
|
|
57
|
+
}
|
|
58
|
+
} @else {
|
|
59
|
+
<div>Loading todos...</div>
|
|
60
|
+
}
|
|
61
|
+
`,
|
|
62
|
+
})
|
|
63
|
+
export class AllTodosComponent {
|
|
64
|
+
// Direct collection usage - gets all items
|
|
65
|
+
allTodos = injectLiveQuery(todosCollection)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Static Query Functions
|
|
69
|
+
|
|
70
|
+
You can create filtered queries using a query function. Note: The query function is evaluated once and is not reactive to signal changes:
|
|
71
|
+
|
|
72
|
+
import { Component } from "@angular/core"
|
|
73
|
+
import { injectLiveQuery } from "@tanstack/angular-db"
|
|
74
|
+
import { eq } from "@tanstack/db"
|
|
75
|
+
import { todosCollection } from "./collections/todos-collection"
|
|
76
|
+
|
|
77
|
+
@Component({
|
|
78
|
+
selector: "app-todos",
|
|
79
|
+
template: `
|
|
80
|
+
@if (todoQuery.isReady()) {
|
|
81
|
+
@for (todo of todoQuery.data(); track todo.id) {
|
|
82
|
+
<div class="todo-item">
|
|
83
|
+
{{ todo.text }}
|
|
84
|
+
<button (click)="toggleTodo(todo.id)">
|
|
85
|
+
{{ todo.completed ? "Undo" : "Complete" }}
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
}
|
|
89
|
+
} @else {
|
|
90
|
+
<div>Loading todos...</div>
|
|
91
|
+
}
|
|
92
|
+
`,
|
|
93
|
+
})
|
|
94
|
+
export class TodosComponent {
|
|
95
|
+
// Static query - filters for incomplete todos
|
|
96
|
+
// This will not react to signal changes within the function
|
|
97
|
+
todoQuery = injectLiveQuery((q) =>
|
|
98
|
+
q
|
|
99
|
+
.from({ todo: todosCollection })
|
|
100
|
+
.where(({ todo }) => eq(todo.completed, false))
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
toggleTodo(id: number) {
|
|
104
|
+
todosCollection.utils.begin()
|
|
105
|
+
todosCollection.utils.write({
|
|
106
|
+
type: 'update',
|
|
107
|
+
key: id,
|
|
108
|
+
value: { completed: true }
|
|
109
|
+
})
|
|
110
|
+
todosCollection.utils.commit()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Reactive Queries with Parameters
|
|
115
|
+
|
|
116
|
+
For queries that need to react to component state changes, use the reactive parameters overload:
|
|
117
|
+
|
|
118
|
+
import { Component, signal } from "@angular/core"
|
|
119
|
+
import { injectLiveQuery } from "@tanstack/angular-db"
|
|
120
|
+
import { eq } from "@tanstack/db"
|
|
121
|
+
import { todosCollection } from "./collections/todos-collection"
|
|
122
|
+
|
|
123
|
+
@Component({
|
|
124
|
+
selector: "app-project-todos",
|
|
125
|
+
template: `
|
|
126
|
+
<select (change)="selectedProjectId.set(+$any($event).target.value)">
|
|
127
|
+
<option [value]="1">Project 1</option>
|
|
128
|
+
<option [value]="2">Project 2</option>
|
|
129
|
+
<option [value]="3">Project 3</option>
|
|
130
|
+
</select>
|
|
131
|
+
|
|
132
|
+
@if (todoQuery.isReady()) {
|
|
133
|
+
<div>Todos for project {{ selectedProjectId() }}:</div>
|
|
134
|
+
@for (todo of todoQuery.data(); track todo.id) {
|
|
135
|
+
<div class="todo-item">
|
|
136
|
+
{{ todo.text }}
|
|
137
|
+
</div>
|
|
138
|
+
}
|
|
139
|
+
} @else {
|
|
140
|
+
<div>Loading todos...</div>
|
|
141
|
+
}
|
|
142
|
+
`,
|
|
143
|
+
})
|
|
144
|
+
export class ProjectTodosComponent {
|
|
145
|
+
selectedProjectId = signal(1)
|
|
146
|
+
|
|
147
|
+
// Reactive query - automatically recreates when selectedProjectId changes
|
|
148
|
+
todoQuery = injectLiveQuery({
|
|
149
|
+
params: () => ({ projectID: this.selectedProjectId() }),
|
|
150
|
+
query: ({ params, q }) =>
|
|
151
|
+
q
|
|
152
|
+
.from({ todo: todosCollection })
|
|
153
|
+
.where(({ todo }) => eq(todo.completed, false))
|
|
154
|
+
.where(({ todo }) => eq(todo.projectID, params.projectID)),
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Advanced Configuration
|
|
159
|
+
|
|
160
|
+
You can also pass a full configuration object:
|
|
161
|
+
|
|
162
|
+
import { Component } from "@angular/core"
|
|
163
|
+
import { injectLiveQuery } from "@tanstack/angular-db"
|
|
164
|
+
import { eq } from "@tanstack/db"
|
|
165
|
+
import { todosCollection } from "./collections/todos-collection"
|
|
166
|
+
|
|
167
|
+
@Component({
|
|
168
|
+
selector: "app-configured-todos",
|
|
169
|
+
template: `
|
|
170
|
+
@if (todoQuery.isReady()) {
|
|
171
|
+
@for (todo of todoQuery.data(); track todo.id) {
|
|
172
|
+
<div>{{ todo.text }}</div>
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
`,
|
|
176
|
+
})
|
|
177
|
+
export class ConfiguredTodosComponent {
|
|
178
|
+
todoQuery = injectLiveQuery({
|
|
179
|
+
query: (q) =>
|
|
180
|
+
q
|
|
181
|
+
.from({ todo: todosCollection })
|
|
182
|
+
.where(({ todo }) => eq(todo.completed, false))
|
|
183
|
+
.select(({ todo }) => ({
|
|
184
|
+
id: todo.id,
|
|
185
|
+
text: todo.text,
|
|
186
|
+
})),
|
|
187
|
+
startSync: true,
|
|
188
|
+
gcTime: 5000,
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
Important Notes
|
|
193
|
+
|
|
194
|
+
Reactivity Behavior
|
|
195
|
+
|
|
196
|
+
- Direct collection: Automatically reactive to collection changes
|
|
197
|
+
- Static query function: Query is built once and is not reactive to signals read within the function
|
|
198
|
+
- Reactive parameters: Query rebuilds when any signal read in params() changes
|
|
199
|
+
- Collection configuration: Static, not reactive to external signals
|
|
200
|
+
|
|
201
|
+
Lifecycle Management
|
|
202
|
+
|
|
203
|
+
- injectLiveQuery automatically handles subscription cleanup when the component is destroyed
|
|
204
|
+
- Each call to injectLiveQuery creates a new collection instance (no caching/reuse)
|
|
205
|
+
- Collections are started immediately and will sync according to their configuration
|
|
206
|
+
|
|
207
|
+
Template Usage
|
|
208
|
+
|
|
209
|
+
Use Angular's new control flow syntax for best performance:
|
|
210
|
+
|
|
211
|
+
@if (query.isReady()) {
|
|
212
|
+
@for (item of query.data(); track item.id) {
|
|
213
|
+
<div>{{ item.text }}</div>
|
|
214
|
+
}
|
|
215
|
+
} @else if (query.isError()) {
|
|
216
|
+
<div>Error loading data</div>
|
|
217
|
+
} @else {
|
|
218
|
+
<div>Loading...</div>
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
API
|
|
222
|
+
|
|
223
|
+
injectLiveQuery()
|
|
224
|
+
|
|
225
|
+
Angular injection function for TanStack DB live queries. Must be called within an injection context (e.g., component constructor, inject(), or field initializer).
|
|
226
|
+
|
|
227
|
+
Overloads
|
|
228
|
+
|
|
229
|
+
// Direct collection - reactive to collection changes
|
|
230
|
+
function injectLiveQuery<TResult, TKey, TUtils>(
|
|
231
|
+
collection: Collection<TResult, TKey, TUtils>
|
|
232
|
+
): LiveQueryResult<TResult>
|
|
233
|
+
|
|
234
|
+
// Static query function - NOT reactive to signals within function
|
|
235
|
+
function injectLiveQuery<TContext>(
|
|
236
|
+
queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>
|
|
237
|
+
): LiveQueryResult<TContext>
|
|
238
|
+
|
|
239
|
+
// Reactive query with parameters - recreates when params() signals change
|
|
240
|
+
function injectLiveQuery<TContext, TParams>(options: {
|
|
241
|
+
params: () => TParams
|
|
242
|
+
query: (args: {
|
|
243
|
+
params: TParams
|
|
244
|
+
q: InitialQueryBuilder
|
|
245
|
+
}) => QueryBuilder<TContext>
|
|
246
|
+
}): LiveQueryResult<TContext>
|
|
247
|
+
|
|
248
|
+
// Collection configuration - static configuration
|
|
249
|
+
function injectLiveQuery<TContext>(
|
|
250
|
+
config: LiveQueryCollectionConfig<TContext>
|
|
251
|
+
): LiveQueryResult<TContext>
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
|
|
255
|
+
An object with Angular signals:
|
|
256
|
+
|
|
257
|
+
- data: Signal<Array<T>> - Array of query results, automatically updates
|
|
258
|
+
- state: Signal<Map<Key, T>> - Map of results by key, automatically updates
|
|
259
|
+
- collection: Signal<Collection> - The underlying collection instance
|
|
260
|
+
- status: Signal<CollectionStatus> - Current status ('idle' | 'loading' | 'ready' | 'error' | 'cleaned-up')
|
|
261
|
+
- isLoading: Signal<boolean> - true when status is 'loading' or 'initialCommit'
|
|
262
|
+
- isReady: Signal<boolean> - true when status is 'ready'
|
|
263
|
+
- isIdle: Signal<boolean> - true when status is 'idle'
|
|
264
|
+
- isError: Signal<boolean> - true when status is 'error'
|
|
265
|
+
- isCleanedUp: Signal<boolean> - true when status is 'cleaned-up'
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
|
|
269
|
+
- collection - Existing collection to observe directly
|
|
270
|
+
- queryFn - Function that builds a static query using the query builder
|
|
271
|
+
- options.params - Reactive function that returns parameters; triggers query rebuild when accessed signals change
|
|
272
|
+
- options.query - Function that builds a query using parameters and query builder
|
|
273
|
+
- config - Configuration object for creating a live query collection
|
|
274
|
+
|
|
275
|
+
Requirements
|
|
276
|
+
|
|
277
|
+
- Angular 16+ (requires signals support)
|
|
278
|
+
- Must be called within an Angular injection context
|
|
279
|
+
- Automatically handles cleanup when the injector is destroyed
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/angular-db",
|
|
3
|
+
"description": "Angular integration for @tanstack/db",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"author": "Ethan McDaniel",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/TanStack/db.git",
|
|
10
|
+
"directory": "packages/angular-db"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://tanstack.com/db",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"optimistic",
|
|
15
|
+
"angular",
|
|
16
|
+
"typescript"
|
|
17
|
+
],
|
|
18
|
+
"packageManager": "pnpm@10.6.3",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@tanstack/db": "workspace:*"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@angular/common": "^19.0.0",
|
|
24
|
+
"@angular/core": "^19.0.0",
|
|
25
|
+
"@angular/platform-browser-dynamic": "^19.0.0",
|
|
26
|
+
"@vitest/coverage-istanbul": "^3.0.9",
|
|
27
|
+
"rxjs": "^7.8.0",
|
|
28
|
+
"zone.js": "^0.14.0"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/esm/index.d.ts",
|
|
34
|
+
"default": "./dist/esm/index.js"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/cjs/index.d.cts",
|
|
38
|
+
"default": "./dist/cjs/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"src"
|
|
46
|
+
],
|
|
47
|
+
"main": "dist/cjs/index.cjs",
|
|
48
|
+
"module": "dist/esm/index.js",
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@angular/core": ">=16.0.0",
|
|
51
|
+
"rxjs": ">=6.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "vite build",
|
|
55
|
+
"dev": "vite build --watch",
|
|
56
|
+
"test": "npx vitest --run",
|
|
57
|
+
"lint": "eslint . --fix"
|
|
58
|
+
},
|
|
59
|
+
"sideEffects": false,
|
|
60
|
+
"type": "module",
|
|
61
|
+
"types": "dist/esm/index.d.ts"
|
|
62
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DestroyRef,
|
|
3
|
+
assertInInjectionContext,
|
|
4
|
+
computed,
|
|
5
|
+
effect,
|
|
6
|
+
inject,
|
|
7
|
+
signal,
|
|
8
|
+
} from "@angular/core"
|
|
9
|
+
import { createLiveQueryCollection } from "@tanstack/db"
|
|
10
|
+
import type {
|
|
11
|
+
ChangeMessage,
|
|
12
|
+
Collection,
|
|
13
|
+
CollectionStatus,
|
|
14
|
+
Context,
|
|
15
|
+
GetResult,
|
|
16
|
+
InitialQueryBuilder,
|
|
17
|
+
LiveQueryCollectionConfig,
|
|
18
|
+
QueryBuilder,
|
|
19
|
+
} from "@tanstack/db"
|
|
20
|
+
import type { Signal } from "@angular/core"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The result of calling `injectLiveQuery`.
|
|
24
|
+
* Contains reactive signals for the query state and data.
|
|
25
|
+
*/
|
|
26
|
+
export interface InjectLiveQueryResult<
|
|
27
|
+
TResult = any,
|
|
28
|
+
TKey extends string | number = string | number,
|
|
29
|
+
TUtils extends Record<string, any> = Record<string, never>,
|
|
30
|
+
> {
|
|
31
|
+
/** A signal containing the complete state map of results keyed by their ID */
|
|
32
|
+
state: Signal<Map<TKey, TResult>>
|
|
33
|
+
/** A signal containing the results as an array */
|
|
34
|
+
data: Signal<Array<TResult>>
|
|
35
|
+
/** A signal containing the underlying collection instance */
|
|
36
|
+
collection: Signal<Collection<TResult, TKey, TUtils>>
|
|
37
|
+
/** A signal containing the current status of the collection */
|
|
38
|
+
status: Signal<CollectionStatus>
|
|
39
|
+
/** A signal indicating whether the collection is currently loading */
|
|
40
|
+
isLoading: Signal<boolean>
|
|
41
|
+
/** A signal indicating whether the collection is ready */
|
|
42
|
+
isReady: Signal<boolean>
|
|
43
|
+
/** A signal indicating whether the collection is idle */
|
|
44
|
+
isIdle: Signal<boolean>
|
|
45
|
+
/** A signal indicating whether the collection has an error */
|
|
46
|
+
isError: Signal<boolean>
|
|
47
|
+
/** A signal indicating whether the collection has been cleaned up */
|
|
48
|
+
isCleanedUp: Signal<boolean>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function injectLiveQuery<
|
|
52
|
+
TContext extends Context,
|
|
53
|
+
TParams extends any,
|
|
54
|
+
>(options: {
|
|
55
|
+
params: () => TParams
|
|
56
|
+
query: (args: {
|
|
57
|
+
params: TParams
|
|
58
|
+
q: InitialQueryBuilder
|
|
59
|
+
}) => QueryBuilder<TContext>
|
|
60
|
+
}): InjectLiveQueryResult<GetResult<TContext>>
|
|
61
|
+
export function injectLiveQuery<TContext extends Context>(
|
|
62
|
+
queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>
|
|
63
|
+
): InjectLiveQueryResult<GetResult<TContext>>
|
|
64
|
+
export function injectLiveQuery<TContext extends Context>(
|
|
65
|
+
config: LiveQueryCollectionConfig<TContext>
|
|
66
|
+
): InjectLiveQueryResult<GetResult<TContext>>
|
|
67
|
+
export function injectLiveQuery<
|
|
68
|
+
TResult extends object,
|
|
69
|
+
TKey extends string | number,
|
|
70
|
+
TUtils extends Record<string, any>,
|
|
71
|
+
>(
|
|
72
|
+
liveQueryCollection: Collection<TResult, TKey, TUtils>
|
|
73
|
+
): InjectLiveQueryResult<TResult, TKey, TUtils>
|
|
74
|
+
export function injectLiveQuery(opts: any) {
|
|
75
|
+
assertInInjectionContext(injectLiveQuery)
|
|
76
|
+
const destroyRef = inject(DestroyRef)
|
|
77
|
+
|
|
78
|
+
const collection = computed(() => {
|
|
79
|
+
// Check if it's an existing collection
|
|
80
|
+
const isExistingCollection =
|
|
81
|
+
opts &&
|
|
82
|
+
typeof opts === `object` &&
|
|
83
|
+
typeof opts.subscribeChanges === `function` &&
|
|
84
|
+
typeof opts.startSyncImmediate === `function` &&
|
|
85
|
+
typeof opts.id === `string`
|
|
86
|
+
|
|
87
|
+
if (isExistingCollection) {
|
|
88
|
+
return opts
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (typeof opts === `function`) {
|
|
92
|
+
return createLiveQueryCollection({
|
|
93
|
+
query: opts,
|
|
94
|
+
startSync: true,
|
|
95
|
+
gcTime: 0,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if it's reactive query options
|
|
100
|
+
const isReactiveQueryOptions =
|
|
101
|
+
opts &&
|
|
102
|
+
typeof opts === `object` &&
|
|
103
|
+
typeof opts.query === `function` &&
|
|
104
|
+
typeof opts.params === `function`
|
|
105
|
+
|
|
106
|
+
if (isReactiveQueryOptions) {
|
|
107
|
+
const { params, query } = opts
|
|
108
|
+
const currentParams = params()
|
|
109
|
+
return createLiveQueryCollection({
|
|
110
|
+
query: (q) => query({ params: currentParams, q }),
|
|
111
|
+
startSync: true,
|
|
112
|
+
gcTime: 0,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle LiveQueryCollectionConfig objects
|
|
117
|
+
if (opts && typeof opts === `object` && typeof opts.query === `function`) {
|
|
118
|
+
return createLiveQueryCollection(opts)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
throw new Error(`Invalid options provided to injectLiveQuery`)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const state = signal(new Map<string | number, any>())
|
|
125
|
+
const data = signal<Array<any>>([])
|
|
126
|
+
const status = signal<CollectionStatus>(`idle`)
|
|
127
|
+
|
|
128
|
+
const syncDataFromCollection = (
|
|
129
|
+
currentCollection: Collection<any, any, any>
|
|
130
|
+
) => {
|
|
131
|
+
const newState = new Map(currentCollection.entries())
|
|
132
|
+
const newData = Array.from(currentCollection.values())
|
|
133
|
+
|
|
134
|
+
state.set(newState)
|
|
135
|
+
data.set(newData)
|
|
136
|
+
status.set(currentCollection.status)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let unsub: (() => void) | null = null
|
|
140
|
+
const cleanup = () => {
|
|
141
|
+
unsub?.()
|
|
142
|
+
unsub = null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
effect((onCleanup) => {
|
|
146
|
+
const currentCollection = collection()
|
|
147
|
+
|
|
148
|
+
if (!currentCollection) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cleanup()
|
|
153
|
+
|
|
154
|
+
// Initialize immediately with current state
|
|
155
|
+
syncDataFromCollection(currentCollection)
|
|
156
|
+
|
|
157
|
+
// Start sync if idle
|
|
158
|
+
if (currentCollection.status === `idle`) {
|
|
159
|
+
currentCollection.startSyncImmediate()
|
|
160
|
+
// Update status after starting sync
|
|
161
|
+
status.set(currentCollection.status)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Subscribe to changes
|
|
165
|
+
unsub = currentCollection.subscribeChanges(
|
|
166
|
+
(_: Array<ChangeMessage<any>>) => {
|
|
167
|
+
syncDataFromCollection(currentCollection)
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// Handle ready state
|
|
172
|
+
currentCollection.onFirstReady(() => {
|
|
173
|
+
status.set(currentCollection.status)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
onCleanup(cleanup)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
destroyRef.onDestroy(cleanup)
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
state,
|
|
183
|
+
data,
|
|
184
|
+
collection,
|
|
185
|
+
status,
|
|
186
|
+
isLoading: computed(
|
|
187
|
+
() => status() === `loading` || status() === `initialCommit`
|
|
188
|
+
),
|
|
189
|
+
isReady: computed(() => status() === `ready`),
|
|
190
|
+
isIdle: computed(() => status() === `idle`),
|
|
191
|
+
isError: computed(() => status() === `error`),
|
|
192
|
+
isCleanedUp: computed(() => status() === `cleaned-up`),
|
|
193
|
+
}
|
|
194
|
+
}
|