svelte-firekit 0.1.9 → 0.2.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/README.md +550 -354
- package/dist/components/AuthGuard.svelte +64 -0
- package/dist/components/AuthGuard.svelte.d.ts +15 -0
- package/dist/components/Collection.svelte +62 -0
- package/dist/components/Collection.svelte.d.ts +39 -0
- package/dist/components/CustomGuard.svelte +87 -0
- package/dist/components/CustomGuard.svelte.d.ts +13 -0
- package/dist/components/Doc.svelte +56 -0
- package/dist/components/Doc.svelte.d.ts +36 -0
- package/dist/components/DownloadURL.svelte +48 -0
- package/dist/components/DownloadURL.svelte.d.ts +14 -0
- package/dist/components/FirebaseApp.svelte +81 -0
- package/dist/components/FirebaseApp.svelte.d.ts +12 -0
- package/dist/components/Node.svelte +54 -0
- package/dist/components/Node.svelte.d.ts +36 -0
- package/dist/components/SignedIn.svelte +22 -0
- package/dist/components/{firekit/signed-in.svelte.d.ts → SignedIn.svelte.d.ts} +1 -4
- package/dist/components/SignedOut.svelte +32 -0
- package/dist/components/{firekit/signed-out.svelte.d.ts → SignedOut.svelte.d.ts} +3 -5
- package/dist/components/UploadTask.svelte +75 -0
- package/dist/components/UploadTask.svelte.d.ts +33 -0
- package/dist/config.d.ts +29 -6
- package/dist/config.js +37 -74
- package/dist/context.d.ts +46 -0
- package/dist/context.js +56 -0
- package/dist/firebase.d.ts +25 -84
- package/dist/firebase.js +75 -125
- package/dist/index.d.ts +32 -20
- package/dist/index.js +49 -30
- package/dist/services/ai.svelte.d.ts +152 -0
- package/dist/services/ai.svelte.js +302 -0
- package/dist/services/analytics.d.ts +39 -231
- package/dist/services/analytics.js +89 -421
- package/dist/services/app-check.svelte.d.ts +82 -0
- package/dist/services/app-check.svelte.js +159 -0
- package/dist/services/auth.d.ts +63 -352
- package/dist/services/auth.js +353 -718
- package/dist/services/bundles.d.ts +42 -0
- package/dist/services/bundles.js +57 -0
- package/dist/services/callable.d.ts +57 -0
- package/dist/services/callable.js +115 -0
- package/dist/services/collection.svelte.d.ts +154 -221
- package/dist/services/collection.svelte.js +357 -663
- package/dist/services/document.svelte.d.ts +73 -254
- package/dist/services/document.svelte.js +196 -497
- package/dist/services/in-app-messaging.d.ts +46 -0
- package/dist/services/in-app-messaging.js +88 -0
- package/dist/services/messaging.svelte.d.ts +75 -0
- package/dist/services/messaging.svelte.js +190 -0
- package/dist/services/mutations.d.ts +59 -282
- package/dist/services/mutations.js +202 -951
- package/dist/services/performance.d.ts +60 -0
- package/dist/services/performance.js +118 -0
- package/dist/services/presence.svelte.d.ts +21 -89
- package/dist/services/presence.svelte.js +232 -469
- package/dist/services/realtime.svelte.d.ts +54 -125
- package/dist/services/realtime.svelte.js +111 -175
- package/dist/services/remote-config.svelte.d.ts +73 -0
- package/dist/services/remote-config.svelte.js +204 -0
- package/dist/services/storage.svelte.d.ts +81 -208
- package/dist/services/storage.svelte.js +190 -305
- package/dist/services/user.svelte.d.ts +23 -244
- package/dist/services/user.svelte.js +129 -439
- package/dist/types/analytics.d.ts +2 -36
- package/dist/types/analytics.js +0 -5
- package/dist/types/auth.d.ts +15 -85
- package/dist/types/auth.js +0 -17
- package/dist/types/collection.d.ts +31 -225
- package/dist/types/collection.js +5 -51
- package/dist/types/document.d.ts +11 -236
- package/dist/types/document.js +2 -47
- package/dist/types/firebase.d.ts +10 -13
- package/dist/types/firebase.js +3 -9
- package/dist/types/index.d.ts +5 -5
- package/dist/types/index.js +5 -4
- package/dist/types/mutations.d.ts +11 -225
- package/dist/types/mutations.js +6 -51
- package/dist/types/presence.d.ts +5 -158
- package/dist/types/presence.js +0 -20
- package/dist/utils/errors.d.ts +10 -14
- package/dist/utils/errors.js +11 -16
- package/dist/utils/firestore.d.ts +3 -4
- package/dist/utils/firestore.js +7 -10
- package/dist/utils/index.d.ts +4 -4
- package/dist/utils/index.js +4 -8
- package/dist/utils/providers.d.ts +4 -13
- package/dist/utils/providers.js +14 -13
- package/dist/utils/user.d.ts +1 -3
- package/dist/utils/user.js +1 -3
- package/package.json +41 -55
- package/dist/components/docs/doc-content.svelte +0 -19
- package/dist/components/docs/doc-content.svelte.d.ts +0 -6
- package/dist/components/docs/doc-header.svelte +0 -24
- package/dist/components/docs/doc-header.svelte.d.ts +0 -13
- package/dist/components/docs/doc-renderer.svelte +0 -27
- package/dist/components/docs/doc-renderer.svelte.d.ts +0 -8
- package/dist/components/docs/mobile-table-of-contents.svelte +0 -42
- package/dist/components/docs/mobile-table-of-contents.svelte.d.ts +0 -3
- package/dist/components/docs/table-of-contents.svelte +0 -33
- package/dist/components/docs/table-of-contents.svelte.d.ts +0 -4
- package/dist/components/docs/toc.svelte.d.ts +0 -16
- package/dist/components/docs/toc.svelte.js +0 -59
- package/dist/components/firekit/Collection.svelte +0 -122
- package/dist/components/firekit/Collection.svelte.d.ts +0 -27
- package/dist/components/firekit/Doc.svelte +0 -140
- package/dist/components/firekit/Doc.svelte.d.ts +0 -28
- package/dist/components/firekit/Node.svelte +0 -97
- package/dist/components/firekit/Node.svelte.d.ts +0 -23
- package/dist/components/firekit/auth-guard.svelte +0 -120
- package/dist/components/firekit/auth-guard.svelte.d.ts +0 -26
- package/dist/components/firekit/custom-guard.svelte +0 -163
- package/dist/components/firekit/custom-guard.svelte.d.ts +0 -31
- package/dist/components/firekit/download-url.svelte +0 -92
- package/dist/components/firekit/download-url.svelte.d.ts +0 -19
- package/dist/components/firekit/firebase-app.svelte +0 -33
- package/dist/components/firekit/firebase-app.svelte.d.ts +0 -7
- package/dist/components/firekit/node-list.svelte +0 -102
- package/dist/components/firekit/node-list.svelte.d.ts +0 -27
- package/dist/components/firekit/signed-in.svelte +0 -42
- package/dist/components/firekit/signed-out.svelte +0 -42
- package/dist/components/firekit/storage-list.svelte +0 -97
- package/dist/components/firekit/storage-list.svelte.d.ts +0 -26
- package/dist/components/firekit/upload-task.svelte +0 -108
- package/dist/components/firekit/upload-task.svelte.d.ts +0 -24
- package/dist/components/nav/app-sidebar.svelte +0 -175
- package/dist/components/nav/app-sidebar.svelte.d.ts +0 -9
- package/dist/components/nav/auto-breadcrumb.svelte +0 -41
- package/dist/components/nav/auto-breadcrumb.svelte.d.ts +0 -3
- package/dist/components/nav/dark-mode-toggle.svelte +0 -17
- package/dist/components/nav/dark-mode-toggle.svelte.d.ts +0 -18
- package/dist/components/nav/nav-components.svelte +0 -24
- package/dist/components/nav/nav-components.svelte.d.ts +0 -11
- package/dist/components/nav/nav-main.svelte +0 -45
- package/dist/components/nav/nav-main.svelte.d.ts +0 -11
- package/dist/components/nav/nav-secondary.svelte +0 -32
- package/dist/components/nav/nav-secondary.svelte.d.ts +0 -14
- package/dist/components/nav/site-header.svelte +0 -32
- package/dist/components/nav/site-header.svelte.d.ts +0 -18
- package/dist/components/ui/avatar/avatar-fallback.svelte +0 -17
- package/dist/components/ui/avatar/avatar-fallback.svelte.d.ts +0 -4
- package/dist/components/ui/avatar/avatar-image.svelte +0 -17
- package/dist/components/ui/avatar/avatar-image.svelte.d.ts +0 -4
- package/dist/components/ui/avatar/avatar.svelte +0 -19
- package/dist/components/ui/avatar/avatar.svelte.d.ts +0 -4
- package/dist/components/ui/avatar/index.d.ts +0 -4
- package/dist/components/ui/avatar/index.js +0 -6
- package/dist/components/ui/badge/badge.svelte +0 -50
- package/dist/components/ui/badge/badge.svelte.d.ts +0 -32
- package/dist/components/ui/badge/index.d.ts +0 -2
- package/dist/components/ui/badge/index.js +0 -2
- package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +0 -23
- package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte.d.ts +0 -5
- package/dist/components/ui/breadcrumb/breadcrumb-item.svelte +0 -20
- package/dist/components/ui/breadcrumb/breadcrumb-item.svelte.d.ts +0 -5
- package/dist/components/ui/breadcrumb/breadcrumb-link.svelte +0 -31
- package/dist/components/ui/breadcrumb/breadcrumb-link.svelte.d.ts +0 -11
- package/dist/components/ui/breadcrumb/breadcrumb-list.svelte +0 -23
- package/dist/components/ui/breadcrumb/breadcrumb-list.svelte.d.ts +0 -5
- package/dist/components/ui/breadcrumb/breadcrumb-page.svelte +0 -23
- package/dist/components/ui/breadcrumb/breadcrumb-page.svelte.d.ts +0 -5
- package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +0 -27
- package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte.d.ts +0 -5
- package/dist/components/ui/breadcrumb/breadcrumb.svelte +0 -21
- package/dist/components/ui/breadcrumb/breadcrumb.svelte.d.ts +0 -5
- package/dist/components/ui/breadcrumb/index.d.ts +0 -8
- package/dist/components/ui/breadcrumb/index.js +0 -10
- package/dist/components/ui/button/button.svelte +0 -80
- package/dist/components/ui/button/button.svelte.d.ts +0 -58
- package/dist/components/ui/button/index.d.ts +0 -2
- package/dist/components/ui/button/index.js +0 -4
- package/dist/components/ui/card/card-action.svelte +0 -20
- package/dist/components/ui/card/card-action.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-content.svelte +0 -15
- package/dist/components/ui/card/card-content.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-description.svelte +0 -20
- package/dist/components/ui/card/card-description.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-footer.svelte +0 -20
- package/dist/components/ui/card/card-footer.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-header.svelte +0 -23
- package/dist/components/ui/card/card-header.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-title.svelte +0 -20
- package/dist/components/ui/card/card-title.svelte.d.ts +0 -5
- package/dist/components/ui/card/card.svelte +0 -23
- package/dist/components/ui/card/card.svelte.d.ts +0 -5
- package/dist/components/ui/card/index.d.ts +0 -8
- package/dist/components/ui/card/index.js +0 -10
- package/dist/components/ui/chart/chart-container.svelte +0 -80
- package/dist/components/ui/chart/chart-container.svelte.d.ts +0 -9
- package/dist/components/ui/chart/chart-style.svelte +0 -36
- package/dist/components/ui/chart/chart-style.svelte.d.ts +0 -8
- package/dist/components/ui/chart/chart-tooltip.svelte +0 -159
- package/dist/components/ui/chart/chart-tooltip.svelte.d.ts +0 -27
- package/dist/components/ui/chart/chart-utils.d.ts +0 -36
- package/dist/components/ui/chart/chart-utils.js +0 -33
- package/dist/components/ui/chart/index.d.ts +0 -4
- package/dist/components/ui/chart/index.js +0 -4
- package/dist/components/ui/checkbox/checkbox.svelte +0 -36
- package/dist/components/ui/checkbox/checkbox.svelte.d.ts +0 -4
- package/dist/components/ui/checkbox/index.d.ts +0 -2
- package/dist/components/ui/checkbox/index.js +0 -4
- package/dist/components/ui/data-table/data-table.svelte.d.ts +0 -40
- package/dist/components/ui/data-table/data-table.svelte.js +0 -110
- package/dist/components/ui/data-table/flex-render.svelte +0 -36
- package/dist/components/ui/data-table/flex-render.svelte.d.ts +0 -30
- package/dist/components/ui/data-table/index.d.ts +0 -3
- package/dist/components/ui/data-table/index.js +0 -3
- package/dist/components/ui/data-table/render-helpers.d.ts +0 -90
- package/dist/components/ui/data-table/render-helpers.js +0 -99
- package/dist/components/ui/dialog/dialog-close.svelte +0 -7
- package/dist/components/ui/dialog/dialog-close.svelte.d.ts +0 -4
- package/dist/components/ui/dialog/dialog-content.svelte +0 -43
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +0 -11
- package/dist/components/ui/dialog/dialog-description.svelte +0 -17
- package/dist/components/ui/dialog/dialog-description.svelte.d.ts +0 -4
- package/dist/components/ui/dialog/dialog-footer.svelte +0 -20
- package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +0 -5
- package/dist/components/ui/dialog/dialog-header.svelte +0 -20
- package/dist/components/ui/dialog/dialog-header.svelte.d.ts +0 -5
- package/dist/components/ui/dialog/dialog-overlay.svelte +0 -20
- package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +0 -4
- package/dist/components/ui/dialog/dialog-title.svelte +0 -17
- package/dist/components/ui/dialog/dialog-title.svelte.d.ts +0 -4
- package/dist/components/ui/dialog/dialog-trigger.svelte +0 -7
- package/dist/components/ui/dialog/dialog-trigger.svelte.d.ts +0 -4
- package/dist/components/ui/dialog/index.d.ts +0 -11
- package/dist/components/ui/dialog/index.js +0 -14
- package/dist/components/ui/drawer/drawer-close.svelte +0 -7
- package/dist/components/ui/drawer/drawer-close.svelte.d.ts +0 -4
- package/dist/components/ui/drawer/drawer-content.svelte +0 -37
- package/dist/components/ui/drawer/drawer-content.svelte.d.ts +0 -7
- package/dist/components/ui/drawer/drawer-description.svelte +0 -17
- package/dist/components/ui/drawer/drawer-description.svelte.d.ts +0 -4
- package/dist/components/ui/drawer/drawer-footer.svelte +0 -20
- package/dist/components/ui/drawer/drawer-footer.svelte.d.ts +0 -5
- package/dist/components/ui/drawer/drawer-header.svelte +0 -20
- package/dist/components/ui/drawer/drawer-header.svelte.d.ts +0 -5
- package/dist/components/ui/drawer/drawer-nested.svelte +0 -12
- package/dist/components/ui/drawer/drawer-nested.svelte.d.ts +0 -3
- package/dist/components/ui/drawer/drawer-overlay.svelte +0 -20
- package/dist/components/ui/drawer/drawer-overlay.svelte.d.ts +0 -4
- package/dist/components/ui/drawer/drawer-title.svelte +0 -17
- package/dist/components/ui/drawer/drawer-title.svelte.d.ts +0 -4
- package/dist/components/ui/drawer/drawer-trigger.svelte +0 -7
- package/dist/components/ui/drawer/drawer-trigger.svelte.d.ts +0 -4
- package/dist/components/ui/drawer/drawer.svelte +0 -12
- package/dist/components/ui/drawer/drawer.svelte.d.ts +0 -3
- package/dist/components/ui/drawer/index.d.ts +0 -13
- package/dist/components/ui/drawer/index.js +0 -15
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -41
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +0 -9
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -27
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +0 -7
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +0 -22
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +0 -8
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte +0 -7
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte.d.ts +0 -4
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -27
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +0 -8
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +0 -24
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte.d.ts +0 -8
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +0 -16
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +0 -4
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +0 -31
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +0 -4
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -17
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +0 -4
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +0 -20
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +0 -5
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +0 -20
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +0 -4
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +0 -29
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +0 -7
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -7
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +0 -4
- package/dist/components/ui/dropdown-menu/index.d.ts +0 -25
- package/dist/components/ui/dropdown-menu/index.js +0 -17
- package/dist/components/ui/input/index.d.ts +0 -2
- package/dist/components/ui/input/index.js +0 -4
- package/dist/components/ui/input/input.svelte +0 -51
- package/dist/components/ui/input/input.svelte.d.ts +0 -13
- package/dist/components/ui/label/index.d.ts +0 -2
- package/dist/components/ui/label/index.js +0 -4
- package/dist/components/ui/label/label.svelte +0 -20
- package/dist/components/ui/label/label.svelte.d.ts +0 -4
- package/dist/components/ui/scroll-area/index.d.ts +0 -3
- package/dist/components/ui/scroll-area/index.js +0 -5
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +0 -31
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte.d.ts +0 -4
- package/dist/components/ui/scroll-area/scroll-area.svelte +0 -40
- package/dist/components/ui/scroll-area/scroll-area.svelte.d.ts +0 -10
- package/dist/components/ui/select/index.d.ts +0 -11
- package/dist/components/ui/select/index.js +0 -14
- package/dist/components/ui/select/select-content.svelte +0 -40
- package/dist/components/ui/select/select-content.svelte.d.ts +0 -8
- package/dist/components/ui/select/select-group-heading.svelte +0 -21
- package/dist/components/ui/select/select-group-heading.svelte.d.ts +0 -10
- package/dist/components/ui/select/select-group.svelte +0 -7
- package/dist/components/ui/select/select-group.svelte.d.ts +0 -4
- package/dist/components/ui/select/select-item.svelte +0 -38
- package/dist/components/ui/select/select-item.svelte.d.ts +0 -4
- package/dist/components/ui/select/select-label.svelte +0 -20
- package/dist/components/ui/select/select-label.svelte.d.ts +0 -6
- package/dist/components/ui/select/select-scroll-down-button.svelte +0 -20
- package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +0 -4
- package/dist/components/ui/select/select-scroll-up-button.svelte +0 -20
- package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +0 -4
- package/dist/components/ui/select/select-separator.svelte +0 -18
- package/dist/components/ui/select/select-separator.svelte.d.ts +0 -4
- package/dist/components/ui/select/select-trigger.svelte +0 -29
- package/dist/components/ui/select/select-trigger.svelte.d.ts +0 -8
- package/dist/components/ui/separator/index.d.ts +0 -2
- package/dist/components/ui/separator/index.js +0 -4
- package/dist/components/ui/separator/separator.svelte +0 -20
- package/dist/components/ui/separator/separator.svelte.d.ts +0 -4
- package/dist/components/ui/sheet/index.d.ts +0 -11
- package/dist/components/ui/sheet/index.js +0 -14
- package/dist/components/ui/sheet/sheet-close.svelte +0 -7
- package/dist/components/ui/sheet/sheet-close.svelte.d.ts +0 -4
- package/dist/components/ui/sheet/sheet-content.svelte +0 -58
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +0 -35
- package/dist/components/ui/sheet/sheet-description.svelte +0 -17
- package/dist/components/ui/sheet/sheet-description.svelte.d.ts +0 -4
- package/dist/components/ui/sheet/sheet-footer.svelte +0 -20
- package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +0 -5
- package/dist/components/ui/sheet/sheet-header.svelte +0 -20
- package/dist/components/ui/sheet/sheet-header.svelte.d.ts +0 -5
- package/dist/components/ui/sheet/sheet-overlay.svelte +0 -20
- package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +0 -4
- package/dist/components/ui/sheet/sheet-title.svelte +0 -17
- package/dist/components/ui/sheet/sheet-title.svelte.d.ts +0 -4
- package/dist/components/ui/sheet/sheet-trigger.svelte +0 -7
- package/dist/components/ui/sheet/sheet-trigger.svelte.d.ts +0 -4
- package/dist/components/ui/sidebar/constants.d.ts +0 -6
- package/dist/components/ui/sidebar/constants.js +0 -6
- package/dist/components/ui/sidebar/context.svelte.d.ts +0 -42
- package/dist/components/ui/sidebar/context.svelte.js +0 -54
- package/dist/components/ui/sidebar/index.d.ts +0 -25
- package/dist/components/ui/sidebar/index.js +0 -27
- package/dist/components/ui/sidebar/sidebar-content.svelte +0 -24
- package/dist/components/ui/sidebar/sidebar-content.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-footer.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-footer.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-group-action.svelte +0 -36
- package/dist/components/ui/sidebar/sidebar-group-action.svelte.d.ts +0 -11
- package/dist/components/ui/sidebar/sidebar-group-content.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-group-content.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-group-label.svelte +0 -34
- package/dist/components/ui/sidebar/sidebar-group-label.svelte.d.ts +0 -11
- package/dist/components/ui/sidebar/sidebar-group.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-group.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-header.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-header.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-input.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +0 -11
- package/dist/components/ui/sidebar/sidebar-inset.svelte +0 -24
- package/dist/components/ui/sidebar/sidebar-inset.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-menu-action.svelte +0 -43
- package/dist/components/ui/sidebar/sidebar-menu-action.svelte.d.ts +0 -12
- package/dist/components/ui/sidebar/sidebar-menu-badge.svelte +0 -29
- package/dist/components/ui/sidebar/sidebar-menu-badge.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte +0 -103
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte.d.ts +0 -51
- package/dist/components/ui/sidebar/sidebar-menu-item.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-menu-item.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-menu-skeleton.svelte +0 -36
- package/dist/components/ui/sidebar/sidebar-menu-skeleton.svelte.d.ts +0 -8
- package/dist/components/ui/sidebar/sidebar-menu-sub-button.svelte +0 -43
- package/dist/components/ui/sidebar/sidebar-menu-sub-button.svelte.d.ts +0 -13
- package/dist/components/ui/sidebar/sidebar-menu-sub-item.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-menu-sub-item.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-menu-sub.svelte +0 -25
- package/dist/components/ui/sidebar/sidebar-menu-sub.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-menu.svelte +0 -21
- package/dist/components/ui/sidebar/sidebar-menu.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-provider.svelte +0 -53
- package/dist/components/ui/sidebar/sidebar-provider.svelte.d.ts +0 -9
- package/dist/components/ui/sidebar/sidebar-rail.svelte +0 -36
- package/dist/components/ui/sidebar/sidebar-rail.svelte.d.ts +0 -5
- package/dist/components/ui/sidebar/sidebar-separator.svelte +0 -19
- package/dist/components/ui/sidebar/sidebar-separator.svelte.d.ts +0 -13
- package/dist/components/ui/sidebar/sidebar-trigger.svelte +0 -35
- package/dist/components/ui/sidebar/sidebar-trigger.svelte.d.ts +0 -8
- package/dist/components/ui/sidebar/sidebar.svelte +0 -104
- package/dist/components/ui/sidebar/sidebar.svelte.d.ts +0 -10
- package/dist/components/ui/skeleton/index.d.ts +0 -2
- package/dist/components/ui/skeleton/index.js +0 -4
- package/dist/components/ui/skeleton/skeleton.svelte +0 -17
- package/dist/components/ui/skeleton/skeleton.svelte.d.ts +0 -5
- package/dist/components/ui/table/index.d.ts +0 -9
- package/dist/components/ui/table/index.js +0 -11
- package/dist/components/ui/table/table-body.svelte +0 -20
- package/dist/components/ui/table/table-body.svelte.d.ts +0 -5
- package/dist/components/ui/table/table-caption.svelte +0 -20
- package/dist/components/ui/table/table-caption.svelte.d.ts +0 -5
- package/dist/components/ui/table/table-cell.svelte +0 -20
- package/dist/components/ui/table/table-cell.svelte.d.ts +0 -5
- package/dist/components/ui/table/table-footer.svelte +0 -20
- package/dist/components/ui/table/table-footer.svelte.d.ts +0 -5
- package/dist/components/ui/table/table-head.svelte +0 -23
- package/dist/components/ui/table/table-head.svelte.d.ts +0 -5
- package/dist/components/ui/table/table-header.svelte +0 -20
- package/dist/components/ui/table/table-header.svelte.d.ts +0 -5
- package/dist/components/ui/table/table-row.svelte +0 -23
- package/dist/components/ui/table/table-row.svelte.d.ts +0 -5
- package/dist/components/ui/table/table.svelte +0 -22
- package/dist/components/ui/table/table.svelte.d.ts +0 -5
- package/dist/components/ui/tabs/index.d.ts +0 -5
- package/dist/components/ui/tabs/index.js +0 -7
- package/dist/components/ui/tabs/tabs-content.svelte +0 -17
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs-list.svelte +0 -20
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs-trigger.svelte +0 -20
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs.svelte +0 -19
- package/dist/components/ui/tabs/tabs.svelte.d.ts +0 -4
- package/dist/components/ui/toggle/index.d.ts +0 -3
- package/dist/components/ui/toggle/index.js +0 -5
- package/dist/components/ui/toggle/toggle.svelte +0 -52
- package/dist/components/ui/toggle/toggle.svelte.d.ts +0 -43
- package/dist/components/ui/toggle-group/index.d.ts +0 -3
- package/dist/components/ui/toggle-group/index.js +0 -5
- package/dist/components/ui/toggle-group/toggle-group-item.svelte +0 -34
- package/dist/components/ui/toggle-group/toggle-group-item.svelte.d.ts +0 -6
- package/dist/components/ui/toggle-group/toggle-group.svelte +0 -47
- package/dist/components/ui/toggle-group/toggle-group.svelte.d.ts +0 -8
- package/dist/components/ui/tooltip/index.d.ts +0 -7
- package/dist/components/ui/tooltip/index.js +0 -9
- package/dist/components/ui/tooltip/tooltip-content.svelte +0 -47
- package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +0 -7
- package/dist/components/ui/tooltip/tooltip-trigger.svelte +0 -7
- package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +0 -4
- package/dist/hooks/is-mobile.svelte.d.ts +0 -4
- package/dist/hooks/is-mobile.svelte.js +0 -7
- package/dist/services/index.d.ts +0 -9
- package/dist/services/index.js +0 -10
- package/dist/types/docs.d.ts +0 -67
- package/dist/types/docs.js +0 -1
- package/dist/utils.d.ts +0 -15
- package/dist/utils.js +0 -44
|
@@ -1,16 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
* @fileoverview FirekitCollection - Optimized collection management for Svelte applications
|
|
3
|
-
* @module FirekitCollection
|
|
4
|
-
* @version 1.0.0
|
|
5
|
-
*/
|
|
6
|
-
import { collection, collectionGroup, query, onSnapshot, getDocs, where, orderBy, limit, startAt, startAfter, endAt, endBefore } from 'firebase/firestore';
|
|
1
|
+
import { collection, collectionGroup, query, getDocs, getDocsFromServer, onSnapshot, orderBy, limit, limitToLast, where, startAt, startAfter, endAt, endBefore, getCountFromServer } from 'firebase/firestore';
|
|
7
2
|
import { firebaseService } from '../firebase.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
3
|
+
import { CollectionError, CollectionErrorCode } from '../types/collection.js';
|
|
4
|
+
function getDb() {
|
|
5
|
+
const db = firebaseService.getDbInstance();
|
|
6
|
+
if (!db)
|
|
7
|
+
throw new Error('Firestore is not initialized.');
|
|
8
|
+
return db;
|
|
9
|
+
}
|
|
10
|
+
function mapFirestoreError(err, path) {
|
|
11
|
+
const codeMap = {
|
|
12
|
+
'permission-denied': CollectionErrorCode.PERMISSION_DENIED,
|
|
13
|
+
'unauthenticated': CollectionErrorCode.UNAUTHENTICATED,
|
|
14
|
+
'not-found': CollectionErrorCode.NOT_FOUND,
|
|
15
|
+
'already-exists': CollectionErrorCode.ALREADY_EXISTS,
|
|
16
|
+
'unavailable': CollectionErrorCode.UNAVAILABLE,
|
|
17
|
+
'deadline-exceeded': CollectionErrorCode.DEADLINE_EXCEEDED,
|
|
18
|
+
'cancelled': CollectionErrorCode.CANCELLED,
|
|
19
|
+
'internal': CollectionErrorCode.INTERNAL_ERROR,
|
|
20
|
+
'data-loss': CollectionErrorCode.DATA_LOSS,
|
|
21
|
+
'failed-precondition': CollectionErrorCode.FAILED_PRECONDITION,
|
|
22
|
+
'aborted': CollectionErrorCode.ABORTED,
|
|
23
|
+
'out-of-range': CollectionErrorCode.OUT_OF_RANGE,
|
|
24
|
+
'unimplemented': CollectionErrorCode.UNIMPLEMENTED,
|
|
25
|
+
'resource-exhausted': CollectionErrorCode.RESOURCE_EXHAUSTED
|
|
26
|
+
};
|
|
27
|
+
const code = codeMap[err.code] ?? CollectionErrorCode.UNKNOWN;
|
|
28
|
+
return new CollectionError(code, err.message, path, undefined, err);
|
|
29
|
+
}
|
|
30
|
+
// ── Query builder ─────────────────────────────────────────────────────────────
|
|
10
31
|
/**
|
|
11
|
-
*
|
|
32
|
+
* Fluent query builder — wraps Firestore QueryConstraints.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const constraints = new FirekitQueryBuilder<Post>()
|
|
37
|
+
* .where('published', '==', true)
|
|
38
|
+
* .orderBy('createdAt', 'desc')
|
|
39
|
+
* .limit(20)
|
|
40
|
+
* .build();
|
|
41
|
+
* ```
|
|
12
42
|
*/
|
|
13
|
-
class FirekitQueryBuilder {
|
|
43
|
+
export class FirekitQueryBuilder {
|
|
14
44
|
constraints = [];
|
|
15
45
|
where(field, operator, value) {
|
|
16
46
|
this.constraints.push(where(field, operator, value));
|
|
@@ -24,6 +54,10 @@ class FirekitQueryBuilder {
|
|
|
24
54
|
this.constraints.push(limit(count));
|
|
25
55
|
return this;
|
|
26
56
|
}
|
|
57
|
+
limitToLast(count) {
|
|
58
|
+
this.constraints.push(limitToLast(count));
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
27
61
|
startAt(...values) {
|
|
28
62
|
this.constraints.push(startAt(...values));
|
|
29
63
|
return this;
|
|
@@ -43,754 +77,414 @@ class FirekitQueryBuilder {
|
|
|
43
77
|
build() {
|
|
44
78
|
return [...this.constraints];
|
|
45
79
|
}
|
|
80
|
+
/** Convenience — returns the constructed Firestore Query for a given collection ref. */
|
|
81
|
+
buildQuery(ref) {
|
|
82
|
+
return query(ref, ...this.constraints);
|
|
83
|
+
}
|
|
46
84
|
}
|
|
85
|
+
// ── Main class ────────────────────────────────────────────────────────────────
|
|
47
86
|
/**
|
|
48
|
-
*
|
|
49
|
-
* Uses Svelte 5 runes for optimal reactivity and performance.
|
|
87
|
+
* Reactive Firestore collection subscription.
|
|
50
88
|
*
|
|
51
|
-
*
|
|
52
|
-
* @template T Document data type
|
|
89
|
+
* Manages a Firestore collection query with reactive state via Svelte 5 runes.
|
|
53
90
|
*
|
|
54
91
|
* @example
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* }
|
|
62
|
-
*
|
|
63
|
-
* // Simple collection subscription
|
|
64
|
-
* const users = firekitCollection<User>('users');
|
|
65
|
-
*
|
|
66
|
-
* // With query constraints
|
|
67
|
-
* const activeUsers = firekitCollection<User>('users',
|
|
68
|
-
* where('active', '==', true),
|
|
69
|
-
* orderBy('name'),
|
|
70
|
-
* limit(10)
|
|
71
|
-
* );
|
|
72
|
-
*
|
|
73
|
-
* // With advanced options
|
|
74
|
-
* const paginatedUsers = firekitCollection<User>('users', {
|
|
75
|
-
* pagination: { enabled: true, pageSize: 20 },
|
|
76
|
-
* cache: { enabled: true, ttl: 300000 },
|
|
77
|
-
* transform: (doc) => ({ ...doc, displayName: doc.name.toUpperCase() })
|
|
78
|
-
* });
|
|
79
|
-
*
|
|
80
|
-
* // Access reactive state
|
|
81
|
-
* $: if (users.loading) {
|
|
82
|
-
* console.log('Loading...');
|
|
83
|
-
* } else if (users.error) {
|
|
84
|
-
* console.error('Error:', users.error);
|
|
85
|
-
* } else {
|
|
86
|
-
* console.log('Users:', users.data);
|
|
87
|
-
* }
|
|
92
|
+
* ```svelte
|
|
93
|
+
* <script lang="ts">
|
|
94
|
+
* import { firekitCollection } from 'svelte-firekit';
|
|
95
|
+
* const posts = firekitCollection<Post>('posts', [where('published', '==', true)]);
|
|
96
|
+
* </script>
|
|
97
|
+
* {#each posts.data as post}...{/each}
|
|
88
98
|
* ```
|
|
89
99
|
*/
|
|
90
|
-
class FirekitCollection {
|
|
91
|
-
// Reactive state
|
|
100
|
+
export class FirekitCollection {
|
|
101
|
+
// ── Reactive state ────────────────────────────────────────────────────────
|
|
92
102
|
_data = $state([]);
|
|
93
103
|
_loading = $state(true);
|
|
94
|
-
_initialized = $state(false);
|
|
95
104
|
_error = $state(null);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Initialize statistics object
|
|
129
|
-
*/
|
|
130
|
-
initializeStats() {
|
|
131
|
-
return {
|
|
132
|
-
totalDocuments: 0,
|
|
133
|
-
readCount: 0,
|
|
134
|
-
writeCount: 0,
|
|
135
|
-
cacheHitRate: 0,
|
|
136
|
-
averageQueryTime: 0,
|
|
137
|
-
lastActivity: new Date(),
|
|
138
|
-
memoryUsage: 0
|
|
105
|
+
_size = $state(0);
|
|
106
|
+
_fromCache = $state(false);
|
|
107
|
+
_hasPendingWrites = $state(false);
|
|
108
|
+
// ── Derived ───────────────────────────────────────────────────────────────
|
|
109
|
+
isEmpty = $derived(!this._loading && this._size === 0);
|
|
110
|
+
hasData = $derived(this._size > 0);
|
|
111
|
+
isReady = $derived(!this._loading && this._error === null);
|
|
112
|
+
// ── Pagination state ──────────────────────────────────────────────────────
|
|
113
|
+
_pageSize = $state(0); // 0 = pagination disabled
|
|
114
|
+
_currentPage = $state(1);
|
|
115
|
+
_hasMore = $state(false);
|
|
116
|
+
// Cursors are not reactive — they are internal implementation detail
|
|
117
|
+
_lastVisible = null;
|
|
118
|
+
_cursorStack = [];
|
|
119
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
120
|
+
_unsubscribe = null;
|
|
121
|
+
_path;
|
|
122
|
+
_constraints;
|
|
123
|
+
_options;
|
|
124
|
+
_isGroup;
|
|
125
|
+
constructor(path, constraints = [], options = {}, isGroup = false) {
|
|
126
|
+
this._path = path;
|
|
127
|
+
this._constraints = constraints;
|
|
128
|
+
this._isGroup = isGroup;
|
|
129
|
+
this._options = {
|
|
130
|
+
realtime: true,
|
|
131
|
+
includeMetadata: false,
|
|
132
|
+
transform: (doc) => doc,
|
|
133
|
+
filter: () => true,
|
|
134
|
+
...options
|
|
139
135
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
this._init();
|
|
137
|
+
}
|
|
138
|
+
// ── Public getters ────────────────────────────────────────────────────────
|
|
139
|
+
get data() { return this._data; }
|
|
140
|
+
get loading() { return this._loading; }
|
|
141
|
+
get error() { return this._error; }
|
|
142
|
+
get size() { return this._size; }
|
|
143
|
+
get path() { return this._path; }
|
|
144
|
+
get fromCache() { return this._fromCache; }
|
|
145
|
+
get hasPendingWrites() { return this._hasPendingWrites; }
|
|
146
|
+
get pageSize() { return this._pageSize; }
|
|
147
|
+
get currentPage() { return this._currentPage; }
|
|
148
|
+
get hasMore() { return this._hasMore; }
|
|
149
|
+
// ── Initialization ────────────────────────────────────────────────────────
|
|
150
|
+
_buildQuery() {
|
|
151
|
+
const db = getDb();
|
|
152
|
+
const ref = this._isGroup
|
|
153
|
+
? collectionGroup(db, this._path)
|
|
154
|
+
: collection(db, this._path);
|
|
155
|
+
return query(ref, ...this._constraints);
|
|
156
|
+
}
|
|
157
|
+
_init() {
|
|
158
|
+
if (typeof window === 'undefined') {
|
|
159
|
+
this._loading = false;
|
|
146
160
|
return;
|
|
161
|
+
}
|
|
147
162
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw new CollectionError(CollectionErrorCode.COLLECTION_UNAVAILABLE, 'Firestore instance not available', this.collectionPath);
|
|
151
|
-
}
|
|
152
|
-
// Check cache first
|
|
153
|
-
const cacheKey = this.getCacheKey(constraints);
|
|
154
|
-
if (this.options.cache?.enabled && this.isCacheValid(cacheKey)) {
|
|
155
|
-
const cached = this.cache.get(cacheKey);
|
|
156
|
-
this._data = cached.data;
|
|
157
|
-
this._loading = false;
|
|
158
|
-
this._initialized = true;
|
|
159
|
-
this._lastUpdated = cached.timestamp;
|
|
160
|
-
this.stats.cacheHitRate = (this.stats.cacheHitRate + 1) / 2;
|
|
161
|
-
}
|
|
162
|
-
// Create collection reference
|
|
163
|
-
this.collectionRef = collection(firestore, this.collectionPath);
|
|
164
|
-
// Create query with constraints
|
|
165
|
-
this.queryRef =
|
|
166
|
-
constraints.length > 0 ? query(this.collectionRef, ...constraints) : this.collectionRef;
|
|
167
|
-
// Set up real-time listener or one-time fetch
|
|
168
|
-
if (this.options.realtime !== false) {
|
|
169
|
-
this.setupRealtimeListener();
|
|
163
|
+
if (this._options.realtime) {
|
|
164
|
+
this._subscribe();
|
|
170
165
|
}
|
|
171
166
|
else {
|
|
172
|
-
|
|
167
|
+
this._fetchOnce();
|
|
173
168
|
}
|
|
174
169
|
}
|
|
175
|
-
catch (
|
|
176
|
-
this.
|
|
170
|
+
catch (err) {
|
|
171
|
+
this._error = new CollectionError(CollectionErrorCode.REFERENCE_UNAVAILABLE, err instanceof Error ? err.message : String(err), this._path);
|
|
172
|
+
this._loading = false;
|
|
177
173
|
}
|
|
178
174
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return;
|
|
185
|
-
const options = {
|
|
186
|
-
includeMetadataChanges: this.options.includeMetadata || false
|
|
187
|
-
};
|
|
188
|
-
this.unsubscribe = onSnapshot(this.queryRef, options, (snapshot) => {
|
|
189
|
-
this.processSnapshot(snapshot);
|
|
190
|
-
}, (error) => {
|
|
191
|
-
this.handleError(error);
|
|
175
|
+
_subscribe() {
|
|
176
|
+
const q = this._buildQuery();
|
|
177
|
+
this._unsubscribe = onSnapshot(q, { includeMetadataChanges: this._options.includeMetadata }, (snap) => this._handleSnapshot(snap), (err) => {
|
|
178
|
+
this._error = mapFirestoreError(err, this._path);
|
|
179
|
+
this._loading = false;
|
|
192
180
|
});
|
|
193
181
|
}
|
|
194
|
-
|
|
195
|
-
* Fetch collection data once (no real-time updates)
|
|
196
|
-
*/
|
|
197
|
-
async fetchOnce() {
|
|
198
|
-
if (!this.queryRef)
|
|
199
|
-
return;
|
|
182
|
+
async _fetchOnce() {
|
|
200
183
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const snapshot = await getDocs(this.queryRef);
|
|
204
|
-
const queryTime = Date.now() - startTime;
|
|
205
|
-
this.stats.averageQueryTime = (this.stats.averageQueryTime + queryTime) / 2;
|
|
206
|
-
this.processSnapshot(snapshot);
|
|
184
|
+
const snap = await getDocs(this._buildQuery());
|
|
185
|
+
this._handleSnapshot(snap);
|
|
207
186
|
}
|
|
208
|
-
catch (
|
|
209
|
-
this.
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Process query snapshot and update state
|
|
214
|
-
*/
|
|
215
|
-
processSnapshot(snapshot) {
|
|
216
|
-
try {
|
|
217
|
-
const startTime = Date.now();
|
|
218
|
-
// Extract documents with ID
|
|
219
|
-
let documents = snapshot.docs.map((doc) => {
|
|
220
|
-
const data = doc.data();
|
|
221
|
-
return { id: doc.id, ...data };
|
|
222
|
-
});
|
|
223
|
-
// Apply transform function if provided
|
|
224
|
-
if (this.options.transform) {
|
|
225
|
-
documents = documents.map(this.options.transform);
|
|
226
|
-
}
|
|
227
|
-
// Apply filter function if provided
|
|
228
|
-
if (this.options.filter) {
|
|
229
|
-
documents = documents.filter(this.options.filter);
|
|
230
|
-
}
|
|
231
|
-
// Apply sort function if provided
|
|
232
|
-
if (this.options.sort) {
|
|
233
|
-
documents = documents.sort(this.options.sort);
|
|
234
|
-
}
|
|
235
|
-
// Track document changes for events
|
|
236
|
-
const changes = this.calculateChanges(this._data, documents);
|
|
237
|
-
// Update reactive state
|
|
238
|
-
this._data = documents;
|
|
187
|
+
catch (err) {
|
|
188
|
+
this._error = mapFirestoreError(err, this._path);
|
|
239
189
|
this._loading = false;
|
|
240
|
-
this._initialized = true;
|
|
241
|
-
this._error = null;
|
|
242
|
-
this._lastUpdated = new Date();
|
|
243
|
-
// Update statistics
|
|
244
|
-
this.stats.totalDocuments = documents.length;
|
|
245
|
-
this.stats.readCount++;
|
|
246
|
-
this.stats.lastActivity = new Date();
|
|
247
|
-
this.stats.memoryUsage = this.calculateMemoryUsage(documents);
|
|
248
|
-
// Update cache if enabled
|
|
249
|
-
if (this.options.cache?.enabled) {
|
|
250
|
-
const cacheKey = this.getCacheKey([]);
|
|
251
|
-
this.cache.set(cacheKey, {
|
|
252
|
-
data: documents,
|
|
253
|
-
timestamp: new Date()
|
|
254
|
-
});
|
|
255
|
-
this.cleanupCache();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
catch (error) {
|
|
259
|
-
this.handleError(error);
|
|
260
190
|
}
|
|
261
191
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const changes = [];
|
|
267
|
-
const oldMap = new Map(oldDocs.map((doc, index) => [doc.id, { doc, index }]));
|
|
268
|
-
const newMap = new Map(newDocs.map((doc, index) => [doc.id, { doc, index }]));
|
|
269
|
-
// Find added and modified documents
|
|
270
|
-
newDocs.forEach((newDoc, newIndex) => {
|
|
271
|
-
const id = newDoc.id;
|
|
272
|
-
const oldEntry = oldMap.get(id);
|
|
273
|
-
if (!oldEntry) {
|
|
274
|
-
// Document was added
|
|
275
|
-
changes.push({
|
|
276
|
-
type: 'added',
|
|
277
|
-
doc: newDoc,
|
|
278
|
-
oldIndex: -1,
|
|
279
|
-
newIndex,
|
|
280
|
-
timestamp: new Date()
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
else if (JSON.stringify(oldEntry.doc) !== JSON.stringify(newDoc)) {
|
|
284
|
-
// Document was modified
|
|
285
|
-
changes.push({
|
|
286
|
-
type: 'modified',
|
|
287
|
-
doc: newDoc,
|
|
288
|
-
oldIndex: oldEntry.index,
|
|
289
|
-
newIndex,
|
|
290
|
-
timestamp: new Date()
|
|
291
|
-
});
|
|
292
|
-
}
|
|
192
|
+
_handleSnapshot(snap) {
|
|
193
|
+
const docs = snap.docs.map((d) => {
|
|
194
|
+
const raw = { id: d.id, ...d.data() };
|
|
195
|
+
return this._options.transform(raw);
|
|
293
196
|
});
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
type: 'removed',
|
|
300
|
-
doc: oldDoc,
|
|
301
|
-
oldIndex,
|
|
302
|
-
newIndex: -1,
|
|
303
|
-
timestamp: new Date()
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
return changes;
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Handle and process errors
|
|
311
|
-
*/
|
|
312
|
-
handleError(error) {
|
|
313
|
-
let collectionError;
|
|
314
|
-
if (error instanceof CollectionError) {
|
|
315
|
-
collectionError = error;
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
// Map Firestore errors to CollectionError
|
|
319
|
-
const code = this.mapFirestoreErrorCode(error.code);
|
|
320
|
-
collectionError = new CollectionError(code, error.message || 'An unknown error occurred', this.collectionPath, [], error);
|
|
321
|
-
}
|
|
322
|
-
this._error = collectionError;
|
|
197
|
+
this._data = this._options.filter ? docs.filter(this._options.filter) : docs;
|
|
198
|
+
this._size = this._data.length;
|
|
199
|
+
this._fromCache = snap.metadata.fromCache;
|
|
200
|
+
this._hasPendingWrites = snap.metadata.hasPendingWrites;
|
|
201
|
+
this._error = null;
|
|
323
202
|
this._loading = false;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
* Map Firestore error codes to CollectionErrorCode
|
|
328
|
-
*/
|
|
329
|
-
mapFirestoreErrorCode(firestoreCode) {
|
|
330
|
-
switch (firestoreCode) {
|
|
331
|
-
case 'permission-denied':
|
|
332
|
-
return CollectionErrorCode.PERMISSION_DENIED;
|
|
333
|
-
case 'not-found':
|
|
334
|
-
return CollectionErrorCode.NOT_FOUND;
|
|
335
|
-
case 'unavailable':
|
|
336
|
-
return CollectionErrorCode.UNAVAILABLE;
|
|
337
|
-
case 'deadline-exceeded':
|
|
338
|
-
return CollectionErrorCode.DEADLINE_EXCEEDED;
|
|
339
|
-
case 'unauthenticated':
|
|
340
|
-
return CollectionErrorCode.UNAUTHENTICATED;
|
|
341
|
-
case 'resource-exhausted':
|
|
342
|
-
return CollectionErrorCode.RESOURCE_EXHAUSTED;
|
|
343
|
-
case 'failed-precondition':
|
|
344
|
-
return CollectionErrorCode.FAILED_PRECONDITION;
|
|
345
|
-
case 'aborted':
|
|
346
|
-
return CollectionErrorCode.ABORTED;
|
|
347
|
-
case 'out-of-range':
|
|
348
|
-
return CollectionErrorCode.OUT_OF_RANGE;
|
|
349
|
-
case 'unimplemented':
|
|
350
|
-
return CollectionErrorCode.UNIMPLEMENTED;
|
|
351
|
-
case 'internal':
|
|
352
|
-
return CollectionErrorCode.INTERNAL_ERROR;
|
|
353
|
-
case 'data-loss':
|
|
354
|
-
return CollectionErrorCode.DATA_LOSS;
|
|
355
|
-
case 'cancelled':
|
|
356
|
-
return CollectionErrorCode.CANCELLED;
|
|
357
|
-
default:
|
|
358
|
-
return CollectionErrorCode.UNKNOWN;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Generate cache key for query
|
|
363
|
-
*/
|
|
364
|
-
getCacheKey(constraints) {
|
|
365
|
-
if (this.options.cache?.customKey) {
|
|
366
|
-
return this.options.cache.customKey(this.collectionPath, constraints);
|
|
367
|
-
}
|
|
368
|
-
// More reliable constraint serialization
|
|
369
|
-
const constraintString = constraints
|
|
370
|
-
.map((c) => {
|
|
371
|
-
try {
|
|
372
|
-
return JSON.stringify(c);
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
return c.toString();
|
|
376
|
-
}
|
|
377
|
-
})
|
|
378
|
-
.join('|');
|
|
379
|
-
return `${this.collectionPath}:${constraintString}`;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Check if cache entry is still valid
|
|
383
|
-
*/
|
|
384
|
-
isCacheValid(cacheKey) {
|
|
385
|
-
const cached = this.cache.get(cacheKey);
|
|
386
|
-
if (!cached)
|
|
387
|
-
return false;
|
|
388
|
-
const ttl = this.options.cache?.ttl || 300000; // 5 minutes default
|
|
389
|
-
return Date.now() - cached.timestamp.getTime() < ttl;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Clean up expired cache entries
|
|
393
|
-
*/
|
|
394
|
-
cleanupCache() {
|
|
395
|
-
const maxSize = this.options.cache?.maxSize || 100;
|
|
396
|
-
const ttl = this.options.cache?.ttl || 300000;
|
|
397
|
-
const now = Date.now();
|
|
398
|
-
// Remove expired entries
|
|
399
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
400
|
-
if (now - entry.timestamp.getTime() > ttl) {
|
|
401
|
-
this.cache.delete(key);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// Remove oldest entries if cache is too large
|
|
405
|
-
if (this.cache.size > maxSize) {
|
|
406
|
-
const entries = Array.from(this.cache.entries()).sort((a, b) => a[1].timestamp.getTime() - b[1].timestamp.getTime());
|
|
407
|
-
const toRemove = entries.slice(0, this.cache.size - maxSize);
|
|
408
|
-
toRemove.forEach(([key]) => this.cache.delete(key));
|
|
203
|
+
// Track cursors for pagination
|
|
204
|
+
if (snap.docs.length > 0) {
|
|
205
|
+
this._lastVisible = snap.docs[snap.docs.length - 1];
|
|
409
206
|
}
|
|
410
207
|
}
|
|
208
|
+
// ── Paginated fetch ───────────────────────────────────────────────────────
|
|
411
209
|
/**
|
|
412
|
-
*
|
|
210
|
+
* Fetches a single page. Fetches `pageSize + 1` docs to detect `hasMore`,
|
|
211
|
+
* then trims the result to `pageSize` for display.
|
|
212
|
+
* @param cursor Start after this document. Pass null for the first page.
|
|
213
|
+
* @param append If true, appends to existing data instead of replacing it (for loadMore).
|
|
413
214
|
*/
|
|
414
|
-
|
|
215
|
+
async _fetchPage(cursor, append = false) {
|
|
216
|
+
this._loading = true;
|
|
415
217
|
try {
|
|
416
|
-
|
|
218
|
+
const base = this._buildQuery();
|
|
219
|
+
const paginatedQuery = cursor
|
|
220
|
+
? query(base, startAfter(cursor), limit(this._pageSize + 1))
|
|
221
|
+
: query(base, limit(this._pageSize + 1));
|
|
222
|
+
const snap = await getDocs(paginatedQuery);
|
|
223
|
+
const allDocs = snap.docs;
|
|
224
|
+
this._hasMore = allDocs.length > this._pageSize;
|
|
225
|
+
const pageDocs = allDocs.slice(0, this._pageSize);
|
|
226
|
+
if (pageDocs.length > 0) {
|
|
227
|
+
this._lastVisible = pageDocs[pageDocs.length - 1];
|
|
228
|
+
}
|
|
229
|
+
const mapped = pageDocs.map((d) => {
|
|
230
|
+
const raw = { id: d.id, ...d.data() };
|
|
231
|
+
return this._options.transform(raw);
|
|
232
|
+
});
|
|
233
|
+
const filtered = this._options.filter ? mapped.filter(this._options.filter) : mapped;
|
|
234
|
+
this._data = append ? [...this._data, ...filtered] : filtered;
|
|
235
|
+
this._size = this._data.length;
|
|
236
|
+
this._fromCache = snap.metadata.fromCache;
|
|
237
|
+
this._hasPendingWrites = snap.metadata.hasPendingWrites;
|
|
238
|
+
this._error = null;
|
|
417
239
|
}
|
|
418
|
-
catch {
|
|
419
|
-
|
|
240
|
+
catch (err) {
|
|
241
|
+
this._error = mapFirestoreError(err, this._path);
|
|
420
242
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
// REACTIVE GETTERS (Public API)
|
|
424
|
-
// ========================================
|
|
425
|
-
/**
|
|
426
|
-
* Get current collection data
|
|
427
|
-
*/
|
|
428
|
-
get data() {
|
|
429
|
-
return this._data;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Get loading state
|
|
433
|
-
*/
|
|
434
|
-
get loading() {
|
|
435
|
-
return this._loading;
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Get initialization state
|
|
439
|
-
*/
|
|
440
|
-
get initialized() {
|
|
441
|
-
return this._initialized;
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* Get error state
|
|
445
|
-
*/
|
|
446
|
-
get error() {
|
|
447
|
-
return this._error;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Check if collection is empty
|
|
451
|
-
*/
|
|
452
|
-
get empty() {
|
|
453
|
-
return this._data.length === 0;
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Get number of documents
|
|
457
|
-
*/
|
|
458
|
-
get size() {
|
|
459
|
-
return this._data.length;
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Get last update timestamp
|
|
463
|
-
*/
|
|
464
|
-
get lastUpdated() {
|
|
465
|
-
return this._lastUpdated;
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Get collection reference
|
|
469
|
-
*/
|
|
470
|
-
get ref() {
|
|
471
|
-
if (!this.collectionRef) {
|
|
472
|
-
throw new CollectionError(CollectionErrorCode.REFERENCE_UNAVAILABLE, 'Collection reference not available', this.collectionPath);
|
|
473
|
-
}
|
|
474
|
-
return this.collectionRef;
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Get query reference
|
|
478
|
-
*/
|
|
479
|
-
get queryReference() {
|
|
480
|
-
if (!this.queryRef) {
|
|
481
|
-
throw new CollectionError(CollectionErrorCode.REFERENCE_UNAVAILABLE, 'Query reference not available', this.collectionPath);
|
|
243
|
+
finally {
|
|
244
|
+
this._loading = false;
|
|
482
245
|
}
|
|
483
|
-
return this.queryRef;
|
|
484
246
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
*/
|
|
488
|
-
get path() {
|
|
489
|
-
return this.collectionPath;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Get collection state summary
|
|
493
|
-
*/
|
|
494
|
-
get state() {
|
|
495
|
-
return {
|
|
496
|
-
data: this._data,
|
|
497
|
-
loading: this._loading,
|
|
498
|
-
initialized: this._initialized,
|
|
499
|
-
error: this._error,
|
|
500
|
-
empty: this.empty,
|
|
501
|
-
size: this.size,
|
|
502
|
-
lastUpdated: this._lastUpdated
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
// ========================================
|
|
506
|
-
// PUBLIC METHODS
|
|
507
|
-
// ========================================
|
|
508
|
-
/**
|
|
509
|
-
* Manually refresh collection data
|
|
510
|
-
*/
|
|
247
|
+
// ── Public methods ────────────────────────────────────────────────────────
|
|
248
|
+
/** Re-fetches from cache or server. */
|
|
511
249
|
async refresh() {
|
|
512
|
-
|
|
513
|
-
throw new CollectionError(CollectionErrorCode.REFERENCE_UNAVAILABLE, 'Cannot refresh: query reference not available', this.collectionPath);
|
|
514
|
-
}
|
|
250
|
+
this._loading = true;
|
|
515
251
|
try {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
this.processSnapshot(snapshot);
|
|
252
|
+
const snap = await getDocs(this._buildQuery());
|
|
253
|
+
this._handleSnapshot(snap);
|
|
519
254
|
}
|
|
520
|
-
catch (
|
|
521
|
-
this.
|
|
522
|
-
|
|
255
|
+
catch (err) {
|
|
256
|
+
this._error = mapFirestoreError(err, this._path);
|
|
257
|
+
this._loading = false;
|
|
523
258
|
}
|
|
524
259
|
}
|
|
525
|
-
/**
|
|
526
|
-
* Get fresh data from server (bypassing cache)
|
|
527
|
-
*/
|
|
260
|
+
/** Forces a fetch directly from the Firestore server (bypasses cache). */
|
|
528
261
|
async getFromServer() {
|
|
529
|
-
|
|
530
|
-
throw new CollectionError(CollectionErrorCode.REFERENCE_UNAVAILABLE, 'Cannot fetch: query reference not available', this.collectionPath);
|
|
531
|
-
}
|
|
262
|
+
this._loading = true;
|
|
532
263
|
try {
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
const data = doc.data();
|
|
536
|
-
return { id: doc.id, ...data };
|
|
537
|
-
});
|
|
264
|
+
const snap = await getDocsFromServer(this._buildQuery());
|
|
265
|
+
this._handleSnapshot(snap);
|
|
538
266
|
}
|
|
539
|
-
catch (
|
|
540
|
-
this.
|
|
541
|
-
|
|
267
|
+
catch (err) {
|
|
268
|
+
this._error = mapFirestoreError(err, this._path);
|
|
269
|
+
this._loading = false;
|
|
542
270
|
}
|
|
543
271
|
}
|
|
544
272
|
/**
|
|
545
|
-
*
|
|
273
|
+
* Returns the server-side document count (uses Firestore aggregation query).
|
|
274
|
+
* Does not download the documents.
|
|
546
275
|
*/
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
const newQuery = query(this.queryRef || this.collectionRef, ...constraints);
|
|
552
|
-
const newCollection = new FirekitCollection(this.collectionPath, this.options);
|
|
553
|
-
newCollection.queryRef = newQuery;
|
|
554
|
-
newCollection.initializeCollection([]);
|
|
555
|
-
return newCollection;
|
|
276
|
+
async countFromServer() {
|
|
277
|
+
const snap = await getCountFromServer(this._buildQuery());
|
|
278
|
+
return snap.data().count;
|
|
556
279
|
}
|
|
557
280
|
/**
|
|
558
|
-
*
|
|
281
|
+
* Replaces the current query constraints and re-fetches.
|
|
559
282
|
*/
|
|
560
|
-
|
|
561
|
-
|
|
283
|
+
async setConstraints(constraints) {
|
|
284
|
+
this._unsubscribe?.();
|
|
285
|
+
this._unsubscribe = null;
|
|
286
|
+
this._constraints = constraints;
|
|
287
|
+
this._loading = true;
|
|
288
|
+
if (this._options.realtime) {
|
|
289
|
+
this._subscribe();
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
await this._fetchOnce();
|
|
293
|
+
}
|
|
562
294
|
}
|
|
563
295
|
/**
|
|
564
|
-
*
|
|
296
|
+
* Appends additional constraints (e.g. pagination cursor) and re-fetches.
|
|
565
297
|
*/
|
|
566
|
-
|
|
567
|
-
return this.
|
|
298
|
+
async addConstraints(constraints) {
|
|
299
|
+
return this.setConstraints([...this._constraints, ...constraints]);
|
|
568
300
|
}
|
|
569
|
-
/**
|
|
570
|
-
|
|
571
|
-
|
|
301
|
+
/** Switches between real-time and one-time mode. */
|
|
302
|
+
setRealtimeMode(enabled) {
|
|
303
|
+
if (enabled === this._options.realtime)
|
|
304
|
+
return;
|
|
305
|
+
this._options.realtime = enabled;
|
|
306
|
+
if (!enabled) {
|
|
307
|
+
this._unsubscribe?.();
|
|
308
|
+
this._unsubscribe = null;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
this._subscribe();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// ── Client-side convenience ───────────────────────────────────────────────
|
|
315
|
+
/** Client-side filter (does NOT modify the Firestore query). */
|
|
572
316
|
filter(predicate) {
|
|
573
317
|
return this._data.filter(predicate);
|
|
574
318
|
}
|
|
575
|
-
/**
|
|
576
|
-
* Find first document matching predicate
|
|
577
|
-
*/
|
|
319
|
+
/** Finds the first document matching a predicate. */
|
|
578
320
|
find(predicate) {
|
|
579
321
|
return this._data.find(predicate);
|
|
580
322
|
}
|
|
581
|
-
/**
|
|
582
|
-
* Find document by ID
|
|
583
|
-
*/
|
|
323
|
+
/** Finds a document by its Firestore document ID. */
|
|
584
324
|
findById(id) {
|
|
585
|
-
return this._data.find((
|
|
325
|
+
return this._data.find((d) => d.id === id);
|
|
586
326
|
}
|
|
587
|
-
/**
|
|
588
|
-
* Sort documents by field or custom function
|
|
589
|
-
*/
|
|
327
|
+
/** Client-side sort (returns a new array, does NOT modify stored data). */
|
|
590
328
|
sort(compareFn) {
|
|
591
329
|
return [...this._data].sort(compareFn);
|
|
592
330
|
}
|
|
331
|
+
// ── Pagination ────────────────────────────────────────────────────────────
|
|
593
332
|
/**
|
|
594
|
-
*
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
*
|
|
603
|
-
*/
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
this.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
*
|
|
617
|
-
*/
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
*/
|
|
625
|
-
count(predicate) {
|
|
626
|
-
return predicate ? this._data.filter(predicate).length : this._data.length;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Check if any document matches predicate
|
|
630
|
-
*/
|
|
631
|
-
some(predicate) {
|
|
632
|
-
return this._data.some(predicate);
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Check if all documents match predicate
|
|
636
|
-
*/
|
|
637
|
-
every(predicate) {
|
|
638
|
-
return this._data.every(predicate);
|
|
333
|
+
* Enables cursor-based pagination and loads the first page.
|
|
334
|
+
*
|
|
335
|
+
* Disables the real-time listener — paginated queries use one-time fetches.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* const posts = firekitCollection<Post>('posts', [orderBy('createdAt', 'desc')]);
|
|
340
|
+
* await posts.setPagination(10); // 10 per page
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
async setPagination(pageSize) {
|
|
344
|
+
this._unsubscribe?.();
|
|
345
|
+
this._unsubscribe = null;
|
|
346
|
+
this._options.realtime = false;
|
|
347
|
+
this._pageSize = pageSize;
|
|
348
|
+
this._currentPage = 1;
|
|
349
|
+
this._lastVisible = null;
|
|
350
|
+
this._cursorStack = [];
|
|
351
|
+
await this._fetchPage(null);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Loads the next page, replacing the current data.
|
|
355
|
+
* Call `setPagination()` first.
|
|
356
|
+
*/
|
|
357
|
+
async nextPage() {
|
|
358
|
+
if (this._pageSize === 0 || !this._hasMore || !this._lastVisible)
|
|
359
|
+
return;
|
|
360
|
+
this._cursorStack.push(this._lastVisible);
|
|
361
|
+
await this._fetchPage(this._lastVisible);
|
|
362
|
+
this._currentPage++;
|
|
639
363
|
}
|
|
640
364
|
/**
|
|
641
|
-
*
|
|
365
|
+
* Loads the previous page, replacing the current data.
|
|
642
366
|
*/
|
|
643
|
-
|
|
644
|
-
if (this.
|
|
367
|
+
async prevPage() {
|
|
368
|
+
if (this._pageSize === 0 || this._currentPage <= 1)
|
|
645
369
|
return;
|
|
646
|
-
this.
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
// Set up new mode
|
|
653
|
-
if (realtime) {
|
|
654
|
-
this.setupRealtimeListener();
|
|
655
|
-
}
|
|
370
|
+
this._cursorStack.pop(); // remove current-page cursor
|
|
371
|
+
const prevCursor = this._cursorStack[this._cursorStack.length - 1] ?? null;
|
|
372
|
+
await this._fetchPage(prevCursor);
|
|
373
|
+
this._currentPage--;
|
|
374
|
+
// Always has more going backwards (we came from a later page)
|
|
375
|
+
this._hasMore = true;
|
|
656
376
|
}
|
|
657
377
|
/**
|
|
658
|
-
*
|
|
378
|
+
* Appends the next page to existing data (infinite scroll).
|
|
379
|
+
* Call `setPagination()` first.
|
|
659
380
|
*/
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
381
|
+
async loadMore() {
|
|
382
|
+
if (this._pageSize === 0 || !this._hasMore || !this._lastVisible)
|
|
383
|
+
return;
|
|
384
|
+
this._cursorStack.push(this._lastVisible);
|
|
385
|
+
await this._fetchPage(this._lastVisible, true);
|
|
386
|
+
this._currentPage++;
|
|
663
387
|
}
|
|
664
388
|
/**
|
|
665
|
-
*
|
|
389
|
+
* Resets pagination back to the first page.
|
|
666
390
|
*/
|
|
667
|
-
|
|
668
|
-
|
|
391
|
+
async resetPagination() {
|
|
392
|
+
if (this._pageSize === 0)
|
|
393
|
+
return;
|
|
394
|
+
this._currentPage = 1;
|
|
395
|
+
this._lastVisible = null;
|
|
396
|
+
this._cursorStack = [];
|
|
397
|
+
await this._fetchPage(null);
|
|
669
398
|
}
|
|
399
|
+
// ── Reactive path ─────────────────────────────────────────────────────────
|
|
670
400
|
/**
|
|
671
|
-
*
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
401
|
+
* Changes the collection path and re-fetches.
|
|
402
|
+
* Tears down any existing real-time listener before switching.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```ts
|
|
406
|
+
* const messages = firekitCollection<Message>('rooms/general/messages');
|
|
407
|
+
* // User switches room:
|
|
408
|
+
* messages.setPath('rooms/random/messages');
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
setPath(newPath) {
|
|
412
|
+
this._unsubscribe?.();
|
|
413
|
+
this._unsubscribe = null;
|
|
414
|
+
this._path = newPath;
|
|
415
|
+
this._loading = true;
|
|
416
|
+
this._data = [];
|
|
417
|
+
this._error = null;
|
|
418
|
+
this._lastVisible = null;
|
|
419
|
+
this._cursorStack = [];
|
|
420
|
+
this._currentPage = 1;
|
|
421
|
+
this._init();
|
|
675
422
|
}
|
|
676
423
|
/**
|
|
677
|
-
*
|
|
424
|
+
* Resolves when the collection has finished its initial load.
|
|
678
425
|
*/
|
|
679
|
-
|
|
426
|
+
waitForReady() {
|
|
427
|
+
if (!this._loading)
|
|
428
|
+
return Promise.resolve(this._data);
|
|
429
|
+
// $effect.root creates a reactive scope outside of component initialization,
|
|
430
|
+
// so this works safely whether called inside or outside a Svelte component.
|
|
680
431
|
return new Promise((resolve) => {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
resolve(this._data);
|
|
690
|
-
}
|
|
691
|
-
}, 100);
|
|
692
|
-
// Timeout after 10 seconds
|
|
693
|
-
setTimeout(() => {
|
|
694
|
-
clearInterval(checkInterval);
|
|
695
|
-
resolve(this._data);
|
|
696
|
-
}, 10000);
|
|
432
|
+
const stop = $effect.root(() => {
|
|
433
|
+
$effect(() => {
|
|
434
|
+
if (!this._loading) {
|
|
435
|
+
stop();
|
|
436
|
+
resolve(this._data);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
});
|
|
697
440
|
});
|
|
698
441
|
}
|
|
699
|
-
|
|
700
|
-
// CLEANUP
|
|
701
|
-
// ========================================
|
|
702
|
-
/**
|
|
703
|
-
* Dispose of all resources and cleanup
|
|
704
|
-
*/
|
|
442
|
+
/** Stops the real-time listener and frees resources. */
|
|
705
443
|
dispose() {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
this.unsubscribe();
|
|
709
|
-
this.unsubscribe = null;
|
|
710
|
-
}
|
|
711
|
-
// Clear cache
|
|
712
|
-
this.cache.clear();
|
|
713
|
-
// Reset state
|
|
714
|
-
this._data = [];
|
|
715
|
-
this._loading = false;
|
|
716
|
-
this._initialized = false;
|
|
717
|
-
this._error = null;
|
|
718
|
-
this._lastUpdated = null;
|
|
444
|
+
this._unsubscribe?.();
|
|
445
|
+
this._unsubscribe = null;
|
|
719
446
|
}
|
|
720
447
|
}
|
|
448
|
+
// ── Collection group ──────────────────────────────────────────────────────────
|
|
721
449
|
/**
|
|
722
|
-
*
|
|
450
|
+
* Reactive Firestore collection group query.
|
|
451
|
+
* Queries across all collections sharing the same name (collectionGroup).
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* ```svelte
|
|
455
|
+
* <script lang="ts">
|
|
456
|
+
* import { firekitCollectionGroup } from 'svelte-firekit';
|
|
457
|
+
* // Query 'comments' across all parent documents
|
|
458
|
+
* const allComments = firekitCollectionGroup<Comment>('comments');
|
|
459
|
+
* </script>
|
|
460
|
+
* ```
|
|
723
461
|
*/
|
|
724
|
-
class FirekitCollectionGroup extends FirekitCollection {
|
|
725
|
-
constructor(collectionId,
|
|
726
|
-
|
|
727
|
-
super(`__collection_group__${collectionId}`, {});
|
|
728
|
-
// Parse constructor arguments properly
|
|
729
|
-
if (Array.isArray(constraintsOrOptions)) {
|
|
730
|
-
this.options = {};
|
|
731
|
-
this.initializeCollectionGroup(collectionId, [
|
|
732
|
-
...constraintsOrOptions,
|
|
733
|
-
...additionalConstraints
|
|
734
|
-
]);
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
this.options = constraintsOrOptions || {};
|
|
738
|
-
this.initializeCollectionGroup(collectionId, additionalConstraints);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Initialize collection group subscription
|
|
743
|
-
*/
|
|
744
|
-
async initializeCollectionGroup(collectionId, constraints) {
|
|
745
|
-
if (!browser)
|
|
746
|
-
return;
|
|
747
|
-
try {
|
|
748
|
-
const firestore = firebaseService.getDbInstance();
|
|
749
|
-
if (!firestore) {
|
|
750
|
-
throw new CollectionError(CollectionErrorCode.COLLECTION_UNAVAILABLE, 'Firestore instance not available', collectionId);
|
|
751
|
-
}
|
|
752
|
-
// Create collection group reference
|
|
753
|
-
const groupRef = collectionGroup(firestore, collectionId);
|
|
754
|
-
// Create query with constraints
|
|
755
|
-
this.queryRef =
|
|
756
|
-
constraints.length > 0
|
|
757
|
-
? query(groupRef, ...constraints)
|
|
758
|
-
: groupRef;
|
|
759
|
-
// Set up real-time listener or one-time fetch
|
|
760
|
-
if (this.options.realtime !== false) {
|
|
761
|
-
this.setupRealtimeListener();
|
|
762
|
-
}
|
|
763
|
-
else {
|
|
764
|
-
await this.fetchOnce();
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
catch (error) {
|
|
768
|
-
this.handleError(error);
|
|
769
|
-
}
|
|
462
|
+
export class FirekitCollectionGroup extends FirekitCollection {
|
|
463
|
+
constructor(collectionId, constraints = [], options = {}) {
|
|
464
|
+
super(collectionId, constraints, options, true);
|
|
770
465
|
}
|
|
771
466
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
467
|
+
// ── Factory functions ─────────────────────────────────────────────────────────
|
|
468
|
+
/**
|
|
469
|
+
* Creates a reactive Firestore collection with real-time updates.
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* ```ts
|
|
473
|
+
* const posts = firekitCollection<Post>('posts', [where('published', '==', true)]);
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
export function firekitCollection(path, constraints = [], options) {
|
|
477
|
+
return new FirekitCollection(path, constraints, { ...options, realtime: true });
|
|
779
478
|
}
|
|
780
479
|
/**
|
|
781
|
-
* Creates a one-time collection fetch (no real-time
|
|
480
|
+
* Creates a one-time Firestore collection fetch (no real-time listener).
|
|
782
481
|
*/
|
|
783
|
-
export function firekitCollectionOnce(path,
|
|
784
|
-
return new FirekitCollection(path, { realtime: false }
|
|
482
|
+
export function firekitCollectionOnce(path, constraints = [], options) {
|
|
483
|
+
return new FirekitCollection(path, constraints, { ...options, realtime: false });
|
|
785
484
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
]);
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
return new FirekitCollectionGroup(collectionId, constraintsOrOptions, ...additionalConstraints);
|
|
795
|
-
}
|
|
485
|
+
/**
|
|
486
|
+
* Creates a reactive Firestore collection group query with real-time updates.
|
|
487
|
+
*/
|
|
488
|
+
export function firekitCollectionGroup(collectionId, constraints = [], options) {
|
|
489
|
+
return new FirekitCollectionGroup(collectionId, constraints, { ...options, realtime: true });
|
|
796
490
|
}
|