svelte-firekit 0.0.25 → 0.1.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 +445 -213
- package/dist/components/Collection.svelte +150 -0
- package/dist/components/Collection.svelte.d.ts +27 -0
- package/dist/components/Ddoc.svelte +131 -0
- package/dist/components/Ddoc.svelte.d.ts +28 -0
- package/dist/components/Node.svelte +97 -0
- package/dist/components/Node.svelte.d.ts +23 -0
- package/dist/components/auth-guard.svelte +89 -0
- package/dist/components/auth-guard.svelte.d.ts +26 -0
- package/dist/components/custom-guard.svelte +122 -0
- package/dist/components/custom-guard.svelte.d.ts +31 -0
- package/dist/components/download-url.svelte +92 -0
- package/dist/components/download-url.svelte.d.ts +19 -0
- package/dist/components/firebase-app.svelte +30 -0
- package/dist/components/firebase-app.svelte.d.ts +7 -0
- package/dist/components/node-list.svelte +102 -0
- package/dist/components/node-list.svelte.d.ts +27 -0
- package/dist/components/signed-in.svelte +42 -0
- package/dist/components/signed-in.svelte.d.ts +11 -0
- package/dist/components/signed-out.svelte +42 -0
- package/dist/components/signed-out.svelte.d.ts +11 -0
- package/dist/components/storage-list.svelte +97 -0
- package/dist/components/storage-list.svelte.d.ts +26 -0
- package/dist/components/upload-task.svelte +108 -0
- package/dist/components/upload-task.svelte.d.ts +24 -0
- package/dist/config.js +17 -39
- package/dist/firebase.d.ts +43 -21
- package/dist/firebase.js +121 -35
- package/dist/index.d.ts +21 -13
- package/dist/index.js +27 -15
- package/dist/services/auth.d.ts +397 -0
- package/dist/services/auth.js +882 -0
- package/dist/services/collection.svelte.d.ts +286 -0
- package/dist/services/collection.svelte.js +871 -0
- package/dist/services/document.svelte.d.ts +288 -0
- package/dist/services/document.svelte.js +555 -0
- package/dist/services/mutations.d.ts +336 -0
- package/dist/services/mutations.js +1079 -0
- package/dist/services/presence.svelte.d.ts +141 -0
- package/dist/services/presence.svelte.js +727 -0
- package/dist/{realtime → services}/realtime.svelte.d.ts +3 -1
- package/dist/{realtime → services}/realtime.svelte.js +13 -7
- package/dist/services/storage.svelte.d.ts +257 -0
- package/dist/services/storage.svelte.js +374 -0
- package/dist/services/user.svelte.d.ts +296 -0
- package/dist/services/user.svelte.js +609 -0
- package/dist/types/auth.d.ts +158 -0
- package/dist/types/auth.js +106 -0
- package/dist/types/collection.d.ts +360 -0
- package/dist/types/collection.js +167 -0
- package/dist/types/document.d.ts +342 -0
- package/dist/types/document.js +148 -0
- package/dist/types/firebase.d.ts +44 -0
- package/dist/types/firebase.js +33 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +4 -0
- package/dist/types/mutations.d.ts +387 -0
- package/dist/types/mutations.js +205 -0
- package/dist/types/presence.d.ts +282 -0
- package/dist/types/presence.js +80 -0
- package/dist/utils/errors.d.ts +21 -0
- package/dist/utils/errors.js +35 -0
- package/dist/utils/firestore.d.ts +9 -0
- package/dist/utils/firestore.js +33 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/providers.d.ts +16 -0
- package/dist/utils/providers.js +30 -0
- package/dist/utils/user.d.ts +8 -0
- package/dist/utils/user.js +29 -0
- package/package.json +64 -64
- package/dist/auth/auth.d.ts +0 -117
- package/dist/auth/auth.js +0 -194
- package/dist/auth/presence.svelte.d.ts +0 -139
- package/dist/auth/presence.svelte.js +0 -373
- package/dist/auth/user.svelte.d.ts +0 -112
- package/dist/auth/user.svelte.js +0 -155
- package/dist/firestore/awaitable-doc.svelte.d.ts +0 -141
- package/dist/firestore/awaitable-doc.svelte.js +0 -183
- package/dist/firestore/batch-mutations.svelte.d.ts +0 -140
- package/dist/firestore/batch-mutations.svelte.js +0 -218
- package/dist/firestore/collection-group.svelte.d.ts +0 -78
- package/dist/firestore/collection-group.svelte.js +0 -120
- package/dist/firestore/collection.svelte.d.ts +0 -96
- package/dist/firestore/collection.svelte.js +0 -137
- package/dist/firestore/doc.svelte.d.ts +0 -90
- package/dist/firestore/doc.svelte.js +0 -131
- package/dist/firestore/document-mutations.svelte.d.ts +0 -164
- package/dist/firestore/document-mutations.svelte.js +0 -273
- package/dist/storage/download-url.svelte.d.ts +0 -83
- package/dist/storage/download-url.svelte.js +0 -114
- package/dist/storage/storage-list.svelte.d.ts +0 -89
- package/dist/storage/storage-list.svelte.js +0 -123
- package/dist/storage/upload-task.svelte.d.ts +0 -94
- package/dist/storage/upload-task.svelte.js +0 -138
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FirekitDoc - Optimized Firestore document management for Svelte
|
|
3
|
+
* @module FirekitDoc
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
import { doc, getDoc, onSnapshot } from 'firebase/firestore';
|
|
7
|
+
import { firebaseService } from '../firebase.js';
|
|
8
|
+
import { browser } from '$app/environment';
|
|
9
|
+
import { DocumentErrorCode, DocumentError } from '../types/document.js';
|
|
10
|
+
/**
|
|
11
|
+
* Manages real-time Firestore document subscriptions with reactive state.
|
|
12
|
+
* Uses Svelte 5 runes for optimal reactivity and performance.
|
|
13
|
+
*
|
|
14
|
+
* @class FirekitDoc
|
|
15
|
+
* @template T Document data type
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* interface User {
|
|
20
|
+
* id: string;
|
|
21
|
+
* name: string;
|
|
22
|
+
* email: string;
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* // Create document subscription
|
|
26
|
+
* const userDoc = firekitDoc<User>('users/123', {
|
|
27
|
+
* id: '123',
|
|
28
|
+
* name: 'Loading...',
|
|
29
|
+
* email: ''
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Access reactive state in Svelte component
|
|
33
|
+
* $: if (userDoc.loading) {
|
|
34
|
+
* console.log('Loading user...');
|
|
35
|
+
* } else if (userDoc.error) {
|
|
36
|
+
* console.error('Error:', userDoc.error);
|
|
37
|
+
* } else if (userDoc.exists) {
|
|
38
|
+
* console.log('User data:', userDoc.data);
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
class FirekitDoc {
|
|
43
|
+
/** Document reference */
|
|
44
|
+
docRef = null;
|
|
45
|
+
/** Unsubscribe function for real-time listener */
|
|
46
|
+
unsubscribe = null;
|
|
47
|
+
/** Configuration options */
|
|
48
|
+
options;
|
|
49
|
+
// ========================================
|
|
50
|
+
// REACTIVE STATE (Svelte 5 Runes)
|
|
51
|
+
// ========================================
|
|
52
|
+
/** Current document data - reactive */
|
|
53
|
+
_data = $state(null);
|
|
54
|
+
/** Loading state - reactive */
|
|
55
|
+
_loading = $state(true);
|
|
56
|
+
/** Error state - reactive */
|
|
57
|
+
_error = $state(null);
|
|
58
|
+
/** Document exists state - reactive */
|
|
59
|
+
_exists = $state(false);
|
|
60
|
+
// ========================================
|
|
61
|
+
// ENHANCED DERIVED STATE
|
|
62
|
+
// ========================================
|
|
63
|
+
/** Computed document state with all reactive properties */
|
|
64
|
+
_computedState = $derived({
|
|
65
|
+
data: this._data,
|
|
66
|
+
loading: this._loading,
|
|
67
|
+
error: this._error,
|
|
68
|
+
exists: this._exists,
|
|
69
|
+
id: this.docRef?.id ?? '',
|
|
70
|
+
isEmpty: !this._data && !this._loading && !this._error,
|
|
71
|
+
isReady: !this._loading && !this._error,
|
|
72
|
+
hasData: !!this._data,
|
|
73
|
+
canRetry: this._error?.isRetryable() ?? false,
|
|
74
|
+
isStale: this._data && this.isStale(300000), // 5 minutes default
|
|
75
|
+
status: this.getStatus()
|
|
76
|
+
});
|
|
77
|
+
/** Derived: Whether document is in a valid state for operations */
|
|
78
|
+
_isValid = $derived(this.docRef !== null && !this._loading && !this._error);
|
|
79
|
+
/** Derived: Whether document can be refreshed */
|
|
80
|
+
_canRefresh = $derived(this.docRef !== null && !this._loading);
|
|
81
|
+
/** Derived: Whether document has pending operations */
|
|
82
|
+
_hasPendingOperations = $derived(this._loading || (this._error ? this._error.isRetryable() : false));
|
|
83
|
+
/**
|
|
84
|
+
* Creates a document subscription with real-time updates
|
|
85
|
+
*
|
|
86
|
+
* @param ref Document path or DocumentReference
|
|
87
|
+
* @param startWith Initial data to show while loading
|
|
88
|
+
* @param options Configuration options
|
|
89
|
+
*/
|
|
90
|
+
constructor(ref, startWith, options = {}) {
|
|
91
|
+
this.options = {
|
|
92
|
+
realtime: true,
|
|
93
|
+
includeMetadata: false,
|
|
94
|
+
source: 'default',
|
|
95
|
+
...options
|
|
96
|
+
};
|
|
97
|
+
// Set initial data if provided
|
|
98
|
+
if (startWith) {
|
|
99
|
+
this.updateState({
|
|
100
|
+
data: startWith,
|
|
101
|
+
exists: true,
|
|
102
|
+
loading: false,
|
|
103
|
+
error: null
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// Only initialize in browser environment
|
|
107
|
+
if (browser) {
|
|
108
|
+
this.initializeDocument(ref);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Initialize document reference and subscription
|
|
113
|
+
*/
|
|
114
|
+
async initializeDocument(ref) {
|
|
115
|
+
try {
|
|
116
|
+
const firestore = firebaseService.getDbInstance();
|
|
117
|
+
if (!firestore) {
|
|
118
|
+
throw new DocumentError(DocumentErrorCode.FIRESTORE_UNAVAILABLE, 'Firestore instance not available');
|
|
119
|
+
}
|
|
120
|
+
// Create document reference
|
|
121
|
+
this.docRef = typeof ref === 'string' ? doc(firestore, ref) : ref;
|
|
122
|
+
// Set up real-time listener or one-time fetch
|
|
123
|
+
if (this.options.realtime) {
|
|
124
|
+
this.setupRealtimeListener();
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
await this.fetchOnce();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
this.handleError(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Set up real-time document listener
|
|
136
|
+
*/
|
|
137
|
+
setupRealtimeListener() {
|
|
138
|
+
if (!this.docRef)
|
|
139
|
+
return;
|
|
140
|
+
const options = {
|
|
141
|
+
includeMetadataChanges: this.options.includeMetadata
|
|
142
|
+
};
|
|
143
|
+
this.unsubscribe = onSnapshot(this.docRef, options, (snapshot) => {
|
|
144
|
+
this.processSnapshot(snapshot);
|
|
145
|
+
}, (error) => {
|
|
146
|
+
this.handleError(error);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Fetch document data once (no real-time updates)
|
|
151
|
+
*/
|
|
152
|
+
async fetchOnce() {
|
|
153
|
+
if (!this.docRef)
|
|
154
|
+
return;
|
|
155
|
+
try {
|
|
156
|
+
this.updateState({ loading: true });
|
|
157
|
+
const snapshot = await getDoc(this.docRef);
|
|
158
|
+
this.processSnapshot(snapshot);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
this.handleError(error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Process document snapshot and update state
|
|
166
|
+
*/
|
|
167
|
+
processSnapshot(snapshot) {
|
|
168
|
+
try {
|
|
169
|
+
const exists = snapshot.exists();
|
|
170
|
+
const data = exists ? snapshot.data() : null;
|
|
171
|
+
// Add document ID to data if it exists
|
|
172
|
+
const processedData = data
|
|
173
|
+
? {
|
|
174
|
+
...data,
|
|
175
|
+
id: snapshot.id
|
|
176
|
+
}
|
|
177
|
+
: null;
|
|
178
|
+
// Update reactive state atomically
|
|
179
|
+
this.updateState({
|
|
180
|
+
data: processedData,
|
|
181
|
+
exists,
|
|
182
|
+
loading: false,
|
|
183
|
+
error: null
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
this.handleError(error);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Handle and process errors
|
|
192
|
+
*/
|
|
193
|
+
handleError(error) {
|
|
194
|
+
let documentError;
|
|
195
|
+
if (error instanceof DocumentError) {
|
|
196
|
+
documentError = error;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Map Firestore errors to DocumentError
|
|
200
|
+
const code = this.mapFirestoreErrorCode(error.code);
|
|
201
|
+
documentError = new DocumentError(code, error.message, error);
|
|
202
|
+
}
|
|
203
|
+
this.updateState({
|
|
204
|
+
error: documentError,
|
|
205
|
+
loading: false
|
|
206
|
+
});
|
|
207
|
+
console.error('FirekitDoc error:', documentError);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Map Firestore error codes to DocumentErrorCode
|
|
211
|
+
*/
|
|
212
|
+
mapFirestoreErrorCode(firestoreCode) {
|
|
213
|
+
switch (firestoreCode) {
|
|
214
|
+
case 'permission-denied':
|
|
215
|
+
return DocumentErrorCode.PERMISSION_DENIED;
|
|
216
|
+
case 'not-found':
|
|
217
|
+
return DocumentErrorCode.NOT_FOUND;
|
|
218
|
+
case 'unavailable':
|
|
219
|
+
return DocumentErrorCode.UNAVAILABLE;
|
|
220
|
+
case 'deadline-exceeded':
|
|
221
|
+
return DocumentErrorCode.TIMEOUT;
|
|
222
|
+
case 'cancelled':
|
|
223
|
+
return DocumentErrorCode.CANCELLED;
|
|
224
|
+
default:
|
|
225
|
+
return DocumentErrorCode.UNKNOWN;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ========================================
|
|
229
|
+
// REACTIVE METHODS
|
|
230
|
+
// ========================================
|
|
231
|
+
/**
|
|
232
|
+
* Reactive method: Automatically retry if error is retryable
|
|
233
|
+
* @returns Promise that resolves when retry is complete
|
|
234
|
+
*/
|
|
235
|
+
async retryIfNeeded() {
|
|
236
|
+
if (this._computedState.canRetry) {
|
|
237
|
+
await this.refresh();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Reactive method: Refresh if data is stale
|
|
242
|
+
* @param maxAge Maximum age in milliseconds
|
|
243
|
+
* @returns Promise that resolves when refresh is complete
|
|
244
|
+
*/
|
|
245
|
+
async refreshIfStale(maxAge = 300000) {
|
|
246
|
+
if (this._computedState.isStale) {
|
|
247
|
+
await this.refresh();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Reactive method: Get fresh data if not ready
|
|
252
|
+
* @returns Promise resolving to document data
|
|
253
|
+
*/
|
|
254
|
+
async ensureReady() {
|
|
255
|
+
if (!this._computedState.isReady) {
|
|
256
|
+
await this.refresh();
|
|
257
|
+
}
|
|
258
|
+
return this._data;
|
|
259
|
+
}
|
|
260
|
+
// ========================================
|
|
261
|
+
// STATE MANAGEMENT
|
|
262
|
+
// ========================================
|
|
263
|
+
/**
|
|
264
|
+
* Update state atomically with partial updates
|
|
265
|
+
* @param updates Partial state updates
|
|
266
|
+
*/
|
|
267
|
+
updateState(updates) {
|
|
268
|
+
if (updates.data !== undefined)
|
|
269
|
+
this._data = updates.data;
|
|
270
|
+
if (updates.loading !== undefined)
|
|
271
|
+
this._loading = updates.loading;
|
|
272
|
+
if (updates.error !== undefined)
|
|
273
|
+
this._error = updates.error;
|
|
274
|
+
if (updates.exists !== undefined)
|
|
275
|
+
this._exists = updates.exists;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get human-readable status string
|
|
279
|
+
* @returns Status description
|
|
280
|
+
*/
|
|
281
|
+
getStatus() {
|
|
282
|
+
if (this._loading)
|
|
283
|
+
return 'loading';
|
|
284
|
+
if (this._error)
|
|
285
|
+
return 'error';
|
|
286
|
+
if (this._exists)
|
|
287
|
+
return 'exists';
|
|
288
|
+
return 'not-found';
|
|
289
|
+
}
|
|
290
|
+
// ========================================
|
|
291
|
+
// PUBLIC GETTERS (Reactive State)
|
|
292
|
+
// ========================================
|
|
293
|
+
/**
|
|
294
|
+
* Get current document data
|
|
295
|
+
* @returns Current document data or null
|
|
296
|
+
*/
|
|
297
|
+
get data() {
|
|
298
|
+
return this._data;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get document ID
|
|
302
|
+
* @returns Document ID or empty string if not available
|
|
303
|
+
*/
|
|
304
|
+
get id() {
|
|
305
|
+
return this.docRef?.id ?? '';
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get loading state
|
|
309
|
+
* @returns True if document is currently loading
|
|
310
|
+
*/
|
|
311
|
+
get loading() {
|
|
312
|
+
return this._loading;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get error state
|
|
316
|
+
* @returns Current error or null if no error
|
|
317
|
+
*/
|
|
318
|
+
get error() {
|
|
319
|
+
return this._error;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Check if document exists
|
|
323
|
+
* @returns True if document exists in Firestore
|
|
324
|
+
*/
|
|
325
|
+
get exists() {
|
|
326
|
+
return this._exists;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get document reference
|
|
330
|
+
* @returns Firestore DocumentReference
|
|
331
|
+
* @throws DocumentError if reference is not available
|
|
332
|
+
*/
|
|
333
|
+
get ref() {
|
|
334
|
+
if (!this.docRef) {
|
|
335
|
+
throw new DocumentError(DocumentErrorCode.REFERENCE_UNAVAILABLE, 'Document reference is not available');
|
|
336
|
+
}
|
|
337
|
+
return this.docRef;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get current document state summary
|
|
341
|
+
* @returns Complete document state object
|
|
342
|
+
*/
|
|
343
|
+
get state() {
|
|
344
|
+
return {
|
|
345
|
+
data: this._data,
|
|
346
|
+
exists: this._exists,
|
|
347
|
+
loading: this._loading,
|
|
348
|
+
error: this._error,
|
|
349
|
+
id: this.id
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Get enhanced computed state
|
|
354
|
+
* @returns Computed state with additional reactive properties
|
|
355
|
+
*/
|
|
356
|
+
get computedState() {
|
|
357
|
+
return this._computedState;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check if document is in valid state for operations
|
|
361
|
+
* @returns True if document can be operated on
|
|
362
|
+
*/
|
|
363
|
+
get isValid() {
|
|
364
|
+
return this._isValid;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Check if document can be refreshed
|
|
368
|
+
* @returns True if refresh operation is available
|
|
369
|
+
*/
|
|
370
|
+
get canRefresh() {
|
|
371
|
+
return this._canRefresh;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Check if document has pending operations
|
|
375
|
+
* @returns True if there are ongoing operations
|
|
376
|
+
*/
|
|
377
|
+
get hasPendingOperations() {
|
|
378
|
+
return this._hasPendingOperations;
|
|
379
|
+
}
|
|
380
|
+
// ========================================
|
|
381
|
+
// PUBLIC METHODS
|
|
382
|
+
// ========================================
|
|
383
|
+
/**
|
|
384
|
+
* Manually refresh document data
|
|
385
|
+
* @returns Promise that resolves when refresh is complete
|
|
386
|
+
*/
|
|
387
|
+
async refresh() {
|
|
388
|
+
if (!this.docRef) {
|
|
389
|
+
throw new DocumentError(DocumentErrorCode.REFERENCE_UNAVAILABLE, 'Cannot refresh: document reference not available');
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
this.updateState({ loading: true });
|
|
393
|
+
const snapshot = await getDoc(this.docRef);
|
|
394
|
+
this.processSnapshot(snapshot);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
this.handleError(error);
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get fresh data from server (bypassing cache)
|
|
403
|
+
* @returns Promise resolving to fresh document data
|
|
404
|
+
*/
|
|
405
|
+
async getFromServer() {
|
|
406
|
+
if (!this.docRef) {
|
|
407
|
+
throw new DocumentError(DocumentErrorCode.REFERENCE_UNAVAILABLE, 'Cannot fetch: document reference not available');
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
const snapshot = await getDoc(this.docRef);
|
|
411
|
+
const data = snapshot.exists() ? snapshot.data() : null;
|
|
412
|
+
return data ? { ...data, id: snapshot.id } : null;
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
this.handleError(error);
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Switch between realtime and one-time fetch modes
|
|
421
|
+
* @param realtime Whether to enable real-time updates
|
|
422
|
+
*/
|
|
423
|
+
setRealtimeMode(realtime) {
|
|
424
|
+
if (this.options.realtime === realtime)
|
|
425
|
+
return;
|
|
426
|
+
this.options.realtime = realtime;
|
|
427
|
+
// Clean up existing listener
|
|
428
|
+
if (this.unsubscribe) {
|
|
429
|
+
this.unsubscribe();
|
|
430
|
+
this.unsubscribe = null;
|
|
431
|
+
}
|
|
432
|
+
// Set up new mode
|
|
433
|
+
if (realtime) {
|
|
434
|
+
this.setupRealtimeListener();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Check if document data is stale (for cache management)
|
|
439
|
+
* @param maxAge Maximum age in milliseconds
|
|
440
|
+
* @returns True if data is considered stale
|
|
441
|
+
*/
|
|
442
|
+
isStale(maxAge = 300000) {
|
|
443
|
+
// 5 minutes default
|
|
444
|
+
if (!this._data || !('updatedAt' in this._data))
|
|
445
|
+
return false;
|
|
446
|
+
const updatedAt = this._data.updatedAt;
|
|
447
|
+
if (!updatedAt?.toDate)
|
|
448
|
+
return false;
|
|
449
|
+
const now = new Date().getTime();
|
|
450
|
+
const dataTime = updatedAt.toDate().getTime();
|
|
451
|
+
return now - dataTime > maxAge;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Clean up resources and unsubscribe from listeners
|
|
455
|
+
*/
|
|
456
|
+
dispose() {
|
|
457
|
+
if (this.unsubscribe) {
|
|
458
|
+
this.unsubscribe();
|
|
459
|
+
this.unsubscribe = null;
|
|
460
|
+
}
|
|
461
|
+
// Reset state
|
|
462
|
+
this.updateState({
|
|
463
|
+
data: null,
|
|
464
|
+
loading: false,
|
|
465
|
+
error: null,
|
|
466
|
+
exists: false
|
|
467
|
+
});
|
|
468
|
+
this.docRef = null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// ========================================
|
|
472
|
+
// FACTORY FUNCTIONS
|
|
473
|
+
// ========================================
|
|
474
|
+
/**
|
|
475
|
+
* Creates a reactive document subscription with real-time updates
|
|
476
|
+
*
|
|
477
|
+
* @template T Document data type
|
|
478
|
+
* @param ref Document path or DocumentReference
|
|
479
|
+
* @param startWith Initial data to show while loading
|
|
480
|
+
* @param options Configuration options
|
|
481
|
+
* @returns FirekitDoc instance with reactive state
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```typescript
|
|
485
|
+
* interface User {
|
|
486
|
+
* id: string;
|
|
487
|
+
* name: string;
|
|
488
|
+
* email: string;
|
|
489
|
+
* }
|
|
490
|
+
*
|
|
491
|
+
* // Real-time document subscription
|
|
492
|
+
* const userDoc = firekitDoc<User>('users/123', {
|
|
493
|
+
* id: '123',
|
|
494
|
+
* name: 'Loading...',
|
|
495
|
+
* email: ''
|
|
496
|
+
* });
|
|
497
|
+
*
|
|
498
|
+
* // One-time fetch
|
|
499
|
+
* const userDoc = firekitDoc<User>('users/123', undefined, {
|
|
500
|
+
* realtime: false
|
|
501
|
+
* });
|
|
502
|
+
*
|
|
503
|
+
* // In Svelte component
|
|
504
|
+
* $: if (userDoc.loading) {
|
|
505
|
+
* // Show loading state
|
|
506
|
+
* } else if (userDoc.error) {
|
|
507
|
+
* // Handle error
|
|
508
|
+
* } else if (userDoc.exists) {
|
|
509
|
+
* // Use userDoc.data
|
|
510
|
+
* }
|
|
511
|
+
*
|
|
512
|
+
* // Use reactive methods
|
|
513
|
+
* $: if (userDoc.computedState.canRetry) {
|
|
514
|
+
* userDoc.retryIfNeeded();
|
|
515
|
+
* }
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
export function firekitDoc(ref, startWith, options) {
|
|
519
|
+
return new FirekitDoc(ref, startWith, options);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Creates a one-time document fetch (no real-time updates)
|
|
523
|
+
*
|
|
524
|
+
* @template T Document data type
|
|
525
|
+
* @param ref Document path or DocumentReference
|
|
526
|
+
* @param startWith Initial data to show while loading
|
|
527
|
+
* @returns FirekitDoc instance configured for one-time fetch
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* ```typescript
|
|
531
|
+
* const userDoc = firekitDocOnce<User>('users/123');
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
export function firekitDocOnce(ref, startWith) {
|
|
535
|
+
return new FirekitDoc(ref, startWith, { realtime: false });
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Creates a document subscription with metadata changes included
|
|
539
|
+
*
|
|
540
|
+
* @template T Document data type
|
|
541
|
+
* @param ref Document path or DocumentReference
|
|
542
|
+
* @param startWith Initial data to show while loading
|
|
543
|
+
* @returns FirekitDoc instance with metadata tracking
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```typescript
|
|
547
|
+
* const userDoc = firekitDocWithMetadata<User>('users/123');
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
export function firekitDocWithMetadata(ref, startWith) {
|
|
551
|
+
return new FirekitDoc(ref, startWith, {
|
|
552
|
+
realtime: true,
|
|
553
|
+
includeMetadata: true
|
|
554
|
+
});
|
|
555
|
+
}
|