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.
Files changed (95) hide show
  1. package/README.md +445 -213
  2. package/dist/components/Collection.svelte +150 -0
  3. package/dist/components/Collection.svelte.d.ts +27 -0
  4. package/dist/components/Ddoc.svelte +131 -0
  5. package/dist/components/Ddoc.svelte.d.ts +28 -0
  6. package/dist/components/Node.svelte +97 -0
  7. package/dist/components/Node.svelte.d.ts +23 -0
  8. package/dist/components/auth-guard.svelte +89 -0
  9. package/dist/components/auth-guard.svelte.d.ts +26 -0
  10. package/dist/components/custom-guard.svelte +122 -0
  11. package/dist/components/custom-guard.svelte.d.ts +31 -0
  12. package/dist/components/download-url.svelte +92 -0
  13. package/dist/components/download-url.svelte.d.ts +19 -0
  14. package/dist/components/firebase-app.svelte +30 -0
  15. package/dist/components/firebase-app.svelte.d.ts +7 -0
  16. package/dist/components/node-list.svelte +102 -0
  17. package/dist/components/node-list.svelte.d.ts +27 -0
  18. package/dist/components/signed-in.svelte +42 -0
  19. package/dist/components/signed-in.svelte.d.ts +11 -0
  20. package/dist/components/signed-out.svelte +42 -0
  21. package/dist/components/signed-out.svelte.d.ts +11 -0
  22. package/dist/components/storage-list.svelte +97 -0
  23. package/dist/components/storage-list.svelte.d.ts +26 -0
  24. package/dist/components/upload-task.svelte +108 -0
  25. package/dist/components/upload-task.svelte.d.ts +24 -0
  26. package/dist/config.js +17 -39
  27. package/dist/firebase.d.ts +43 -21
  28. package/dist/firebase.js +121 -35
  29. package/dist/index.d.ts +21 -13
  30. package/dist/index.js +27 -15
  31. package/dist/services/auth.d.ts +397 -0
  32. package/dist/services/auth.js +882 -0
  33. package/dist/services/collection.svelte.d.ts +286 -0
  34. package/dist/services/collection.svelte.js +871 -0
  35. package/dist/services/document.svelte.d.ts +288 -0
  36. package/dist/services/document.svelte.js +555 -0
  37. package/dist/services/mutations.d.ts +336 -0
  38. package/dist/services/mutations.js +1079 -0
  39. package/dist/services/presence.svelte.d.ts +141 -0
  40. package/dist/services/presence.svelte.js +727 -0
  41. package/dist/{realtime → services}/realtime.svelte.d.ts +3 -1
  42. package/dist/{realtime → services}/realtime.svelte.js +13 -7
  43. package/dist/services/storage.svelte.d.ts +257 -0
  44. package/dist/services/storage.svelte.js +374 -0
  45. package/dist/services/user.svelte.d.ts +296 -0
  46. package/dist/services/user.svelte.js +609 -0
  47. package/dist/types/auth.d.ts +158 -0
  48. package/dist/types/auth.js +106 -0
  49. package/dist/types/collection.d.ts +360 -0
  50. package/dist/types/collection.js +167 -0
  51. package/dist/types/document.d.ts +342 -0
  52. package/dist/types/document.js +148 -0
  53. package/dist/types/firebase.d.ts +44 -0
  54. package/dist/types/firebase.js +33 -0
  55. package/dist/types/index.d.ts +6 -0
  56. package/dist/types/index.js +4 -0
  57. package/dist/types/mutations.d.ts +387 -0
  58. package/dist/types/mutations.js +205 -0
  59. package/dist/types/presence.d.ts +282 -0
  60. package/dist/types/presence.js +80 -0
  61. package/dist/utils/errors.d.ts +21 -0
  62. package/dist/utils/errors.js +35 -0
  63. package/dist/utils/firestore.d.ts +9 -0
  64. package/dist/utils/firestore.js +33 -0
  65. package/dist/utils/index.d.ts +4 -0
  66. package/dist/utils/index.js +8 -0
  67. package/dist/utils/providers.d.ts +16 -0
  68. package/dist/utils/providers.js +30 -0
  69. package/dist/utils/user.d.ts +8 -0
  70. package/dist/utils/user.js +29 -0
  71. package/package.json +64 -64
  72. package/dist/auth/auth.d.ts +0 -117
  73. package/dist/auth/auth.js +0 -194
  74. package/dist/auth/presence.svelte.d.ts +0 -139
  75. package/dist/auth/presence.svelte.js +0 -373
  76. package/dist/auth/user.svelte.d.ts +0 -112
  77. package/dist/auth/user.svelte.js +0 -155
  78. package/dist/firestore/awaitable-doc.svelte.d.ts +0 -141
  79. package/dist/firestore/awaitable-doc.svelte.js +0 -183
  80. package/dist/firestore/batch-mutations.svelte.d.ts +0 -140
  81. package/dist/firestore/batch-mutations.svelte.js +0 -218
  82. package/dist/firestore/collection-group.svelte.d.ts +0 -78
  83. package/dist/firestore/collection-group.svelte.js +0 -120
  84. package/dist/firestore/collection.svelte.d.ts +0 -96
  85. package/dist/firestore/collection.svelte.js +0 -137
  86. package/dist/firestore/doc.svelte.d.ts +0 -90
  87. package/dist/firestore/doc.svelte.js +0 -131
  88. package/dist/firestore/document-mutations.svelte.d.ts +0 -164
  89. package/dist/firestore/document-mutations.svelte.js +0 -273
  90. package/dist/storage/download-url.svelte.d.ts +0 -83
  91. package/dist/storage/download-url.svelte.js +0 -114
  92. package/dist/storage/storage-list.svelte.d.ts +0 -89
  93. package/dist/storage/storage-list.svelte.js +0 -123
  94. package/dist/storage/upload-task.svelte.d.ts +0 -94
  95. 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
+ }