├── .firebaserc ├── .github └── workflows │ ├── deploy-docs.yml │ ├── npm-publish.yml │ └── run-tests.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── .vscode │ ├── extensions.json │ └── launch.json ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public │ ├── favicon.svg │ ├── logo.png │ ├── logo.svg │ ├── moon.svg │ └── sun.svg ├── src │ ├── components │ │ ├── Footer.astro │ │ ├── Search.astro │ │ ├── SideNav.astro │ │ └── TopNav.astro │ ├── env.d.ts │ ├── layouts │ │ └── MainLayout.astro │ └── pages │ │ ├── analytics │ │ └── page-view-component.md │ │ ├── api │ │ └── index.md │ │ ├── app │ │ ├── context.md │ │ └── firebase-app.md │ │ ├── auth │ │ ├── signed-in.md │ │ ├── signed-out.md │ │ └── user-store.md │ │ ├── firestore │ │ ├── collection-component.md │ │ ├── collection-store.md │ │ ├── doc-component.md │ │ └── doc-store.md │ │ ├── guide │ │ ├── patterns.md │ │ ├── start.md │ │ └── todo.md │ │ ├── index.md │ │ ├── rtdb │ │ ├── node-component.md │ │ ├── node-list-component.md │ │ ├── node-list-store.md │ │ └── node-store.md │ │ └── storage │ │ ├── download-url.md │ │ ├── storage-list.md │ │ └── upload-task.md ├── tailwind.config.cjs └── tsconfig.json ├── firebase.json ├── package-lock.json ├── package.json ├── playwright.config.ts ├── src ├── app.d.ts ├── app.html ├── lib │ ├── components │ │ ├── Collection.svelte │ │ ├── Doc.svelte │ │ ├── DownloadURL.svelte │ │ ├── FirebaseApp.svelte │ │ ├── Node.svelte │ │ ├── NodeList.svelte │ │ ├── PageView.svelte │ │ ├── SignedIn.svelte │ │ ├── SignedOut.svelte │ │ ├── StorageList.svelte │ │ ├── UploadTask.svelte │ │ └── User.svelte │ ├── index.js │ └── stores │ │ ├── auth.ts │ │ ├── firestore.ts │ │ ├── rtdb.ts │ │ ├── sdk.ts │ │ └── storage.ts └── routes │ ├── +layout.svelte │ ├── +page.svelte │ ├── analytics-test │ ├── +layout.svelte │ ├── +page.server.ts │ ├── +page.svelte │ └── client-side │ │ └── +page.svelte │ ├── auth-test │ └── +page.svelte │ ├── firebase.ts │ ├── firestore-test │ └── +page.svelte │ ├── rtdb-test │ └── +page.svelte │ ├── ssr-test │ ├── +page.svelte │ └── +page.ts │ └── storage-test │ └── +page.svelte ├── static └── favicon.png ├── storage.rules ├── svelte.config.js ├── tests ├── auth.test.ts ├── firestore.test.ts ├── main.test.ts ├── rtdb.test.ts └── storage.test.ts ├── tsconfig.json └── vite.config.js /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "sveltefire-testing" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Firebase Hosting 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | defaults: 9 | run: 10 | working-directory: ./docs 11 | 12 | env: 13 | FIREBASE_TOKEN: ${{secrets.FIREBASE_TOKEN}} 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-node@v2 21 | with: 22 | node-version: "18.x" 23 | - run: npm ci 24 | - run: npm run build 25 | - run: npm run deploy 26 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: "18.x" 15 | registry-url: "https://registry.npmjs.org" 16 | - run: npm ci 17 | - run: npm run build 18 | - name: Publish package to NPM 19 | run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Playwright Tests 2 | 3 | on: 4 | push: 5 | branches: [master, main] 6 | pull_request: 7 | branches: [master, main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | env: 13 | NODE_ENV: ci 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: 18 19 | - run: npm ci 20 | - run: npx playwright install --with-deps chromium 21 | - run: npx playwright test 22 | - uses: actions/upload-artifact@v3 23 | if: always() 24 | with: 25 | name: playwright-report 26 | path: playwright-report/ 27 | retention-days: 30 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | package 12 | /test-results 13 | *-debug.log 14 | /dist 15 | /.firebase -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2024 Fireship LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

SvelteFire

4 | npm 5 | Discord 6 | License 7 |
Cybernetically Enhanced Firebase Apps
8 |
9 |
10 | 11 | 14 | 15 | 16 | A minimal, yet powerful library that puts realtime Firebase data into Svelte stores. 17 | 18 | - [Quick Start](https://sveltefire.fireship.io/guide/start) 19 | - [Documentation](https://sveltefire.fireship.io) 20 | 21 | ## Build Complex Apps Faster 22 | 23 | SvelteFire allows you to access Firebase Auth, Firestore, Storage, RealtimeDB, and Analytics with minimal complexity. It simplfies relational data with a declarative syntax, handles loading states, automatically disposes of realtime data subscriptions, and more! 24 | 25 | Gaze in awe at the example below where we fetch multiple levels of realtime user data with just a few lines of Svelte code: 26 | 27 | ```svelte 28 | 29 | 30 | 31 | 32 | 33 | 34 |

Howdy, {user.uid}

35 | 36 | 37 | 38 | 39 |

{post.title}

40 | 41 | 42 | 43 | {#each comments as comment} 44 | 45 | {/each} 46 | ... 47 | ``` 48 | 49 | Each component in this example above is underpinned by a Svelte store. These custom stores can be used for fine-grained control and to implement your own custom patterns. 50 | 51 | Use stores to access Firebase data with Svelte's reactive `$` syntax: 52 | 53 | ```svelte 54 | 60 | 61 | {$post?.title} 62 | ``` -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /docs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /docs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # SvelteFire Docs Site 2 | 3 | -------------------------------------------------------------------------------- /docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | 3 | import tailwind from "@astrojs/tailwind"; 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | experimental: { 8 | viewTransitions: true 9 | }, 10 | integrations: [tailwind()] 11 | }); -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview", 10 | "astro": "astro", 11 | "deploy": "firebase deploy --debug --token \"$FIREBASE_TOKEN\" --only hosting" 12 | }, 13 | "dependencies": { 14 | "@astrojs/tailwind": "^4.0.0", 15 | "@docsearch/js": "^3.5.1", 16 | "@tailwindcss/typography": "^0.5.9", 17 | "astro": "^2.10.7", 18 | "tailwindcss": "^3.3.3" 19 | }, 20 | "devDependencies": { 21 | "firebase-tools": "^12.4.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codediodeio/sveltefire/b78f52dce8e188fb87d9494fb7631150cc5139b7/docs/public/logo.png -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/public/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/sun.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/src/components/Footer.astro: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /docs/src/components/Search.astro: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /docs/src/components/SideNav.astro: -------------------------------------------------------------------------------- 1 | 32 | 33 | 44 | -------------------------------------------------------------------------------- /docs/src/components/TopNav.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Search from './Search.astro' 3 | --- 4 | 5 | 6 | 66 | 67 | 89 | -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /docs/src/layouts/MainLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { ViewTransitions } from "astro:transitions"; 3 | import SideNav from "../components/SideNav.astro"; 4 | import TopNav from "../components/TopNav.astro"; 5 | import Footer from "../components/Footer.astro"; 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | SvelteFire Documentation 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | 39 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/src/pages/analytics/page-view-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PageView Component 3 | pubDate: 2023-12-23 4 | description: SvelteFire PageView Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # PageView 9 | 10 | The `PageView` component logs a Google analytics `page_view` event when it is mounted. 11 | 12 | ### Slot Props 13 | 14 | - `eventName` - (default: 'page_view') Set the current user as the userId in Google Analytics 15 | - `setUser` - (default: true) Set the current user as the userId in Google Analytics 16 | - `customParams` - (optional) custom parameters to pass to the `signIn` function 17 | 18 | ### Layout Example (recommended) 19 | 20 | The most efficient way to integrate Firebase Analytics is to log events from a layout component. This will ensure that every route change is logged, both on the client and server. Make sure to `key` the `PageView` component so that it is re-mounted on every route change. 21 | 22 | ```svelte 23 | 24 | 28 | 29 | 30 | 31 | {#key $page.route.id} 32 | 33 | {/key} 34 | ``` 35 | 36 | ### Page Example 37 | 38 | For fine-grained control, you can include `PageView` on a page-by-page basis. This is useful when sending custom parameters. 39 | 40 | 41 | ```svelte 42 | 43 | 49 | 50 | 51 | 52 | ``` -------------------------------------------------------------------------------- /docs/src/pages/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Docs 3 | pubDate: 2023-07-23 4 | description: SvelteFire API Docs 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | ## TODO -------------------------------------------------------------------------------- /docs/src/pages/app/context.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: getFirebaseContext 3 | pubDate: 2023-07-23 4 | description: SvelteFire FirebaseApp Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # getFirebaseContext 9 | 10 | Get the Firebase SDK context from a component. 11 | 12 | ### Example 13 | 14 | ```svelte 15 | 19 | ``` -------------------------------------------------------------------------------- /docs/src/pages/app/firebase-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FirebaseApp Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire FirebaseApp Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # FirebaseApp 9 | 10 | Puts the Firebase app into Svelte's context. It should be used as a parent to all other SvelteFire components. 11 | 12 | ### Props 13 | 14 | - `firestore` - Firebase Auth instance 15 | - `auth` - Firestore instance 16 | - `storage` - Storage instance 17 | - `rtdb` - RealtimeDB instance 18 | - `analytics` - Firebase Analytics instance 19 | 20 | 21 | 22 | ### Example 23 | 24 | Initialize Firebase with the SDKs you need in your app, then pass them to `FirebaseApp` as props. 25 | 26 | ```svelte 27 | 38 | 39 | 40 | 41 | 42 | ``` -------------------------------------------------------------------------------- /docs/src/pages/auth/signed-in.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SignedIn Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire SignedIn Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # SignedIn 9 | 10 | The `SignedIn` component renders content for the current user. It is a wrapper around the `userStore`. If the user is not signed in, the children will not be rendered. 11 | 12 | ### Slot Props 13 | 14 | - `user` - The current Firebase user 15 | - `auth` - The current Firebase auth instance 16 | - `signOut` - A function to sign out the current user 17 | 18 | ### Example 19 | 20 | ```svelte 21 | 24 | 25 | 26 |

Howdy, {user.uid}

27 |
28 | 29 | 30 | 31 | 32 | 33 | ``` -------------------------------------------------------------------------------- /docs/src/pages/auth/signed-out.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SignedOut Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire SignedOut Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # SignedOut 9 | 10 | The `SignedOut` component renders content when the current user is `null`. It is a wrapper around the `userStore`. If the user is signed in, the children will not be rendered. 11 | 12 | ### Slot Props 13 | 14 | - `auth` - The current Firebase auth instance 15 | 16 | ### Example 17 | 18 | ```svelte 19 | 23 | 24 | 25 | You must be signed in to see this! 26 | 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/src/pages/auth/user-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: userStore 3 | pubDate: 2023-07-23 4 | description: SvelteFire userStore API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # userStore 9 | 10 | Listens the current Firebase user. 11 | 12 | ### Parameters 13 | 14 | - `auth` - Firebase Auth instance 15 | 16 | ### Example 17 | 18 | ```svelte 19 | 25 | 26 | Hello {$user.uid} 27 | ``` -------------------------------------------------------------------------------- /docs/src/pages/firestore/collection-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collection Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire Collection Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # Collection 9 | 10 | The `Collection` component is a wrapper around the `collectionStore`. It renders the collection data and handles the loading state. 11 | 12 | ### Props 13 | 14 | - `ref` - A Firestore collection reference, query reference, or path string (e.g. `posts`) 15 | - `startWith` - (optional) initial value to use before the collection is fetched 16 | 17 | ### Slots 18 | 19 | - `default` - The collection data 20 | - `loading` - Loading state 21 | 22 | ### Slot Props 23 | 24 | - `data` - An array of document data 25 | - `ref` - The Firestore collection reference 26 | - `firestore` - The Firestore instance 27 | - `count` - The number of documents returned by the query 28 | 29 | ### Example 30 | 31 | ```svelte 32 | 35 | 36 | 37 | 38 |

Found {count} posts

39 | 40 | {#each data as post} 41 |

{post.title}

42 | {/each} 43 | 44 |

Loading...

45 |
46 | ``` -------------------------------------------------------------------------------- /docs/src/pages/firestore/collection-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: collectionStore 3 | pubDate: 2023-07-23 4 | description: SvelteFire collectionStore API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # collectionStore 9 | 10 | Subscribes to Firestore collection data and listens to real-time updates. 11 | 12 | ### Parameters 13 | 14 | - `firestore` - Firestore instance 15 | - `ref` - A Firestore collection reference, query reference, or path string (e.g. `posts`) 16 | - `startWith` - (optional) initial value to use before the document is fetched 17 | 18 | ### Example 19 | 20 | ```svelte 21 | 27 | 28 | {#each $posts as post} 29 |

{post.title}

30 | {/each} 31 | ``` 32 | 33 | 34 | With a query reference: 35 | 36 | ```svelte 37 | 47 | ``` 48 | 49 | With TypeScript: 50 | 51 | ```svelte 52 | 61 | 62 | {#each $posts as post} 63 |

{post.title}

64 | {/each} 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/src/pages/firestore/doc-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Doc Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire Doc Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # Doc 9 | 10 | The `Doc` component is a wrapper around the `docStore`. It renders the document data and handles the loading state. 11 | 12 | ### Props 13 | 14 | - `ref` - A Firestore document reference or path string (e.g. `posts/hi-mom`) 15 | - `startWith` - (optional) initial value to use before the document is fetched 16 | 17 | ### Slots 18 | 19 | - `default` - The document data 20 | - `loading` - Loading state 21 | 22 | ### Slot Props 23 | 24 | - `data` - The document data 25 | - `ref` - The Firestore document reference 26 | - `firestore` - The Firestore instance 27 | 28 | ### Example 29 | 30 | ```svelte 31 | 34 | 35 | 36 |

{data?.title}

37 | 38 |

Loading...

39 |
40 | ``` -------------------------------------------------------------------------------- /docs/src/pages/firestore/doc-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: docStore 3 | pubDate: 2023-07-23 4 | description: SvelteFire docStore API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # docStore 9 | 10 | Subscribes to Firestore document data and listens to realtime updates. 11 | 12 | ### Parameters 13 | 14 | - `firestore` - Firestore instance 15 | - `ref` - A Firestore document reference or path string (e.g. `posts/hi-mom`) 16 | - `startWith` - (optional) initial value to use before the document is fetched 17 | 18 | ### Example 19 | 20 | ```svelte 21 | 27 | 28 | {$post?.title} 29 | ``` 30 | 31 | With a document reference: 32 | 33 | ```svelte 34 | 42 | ``` 43 | 44 | With TypeScript: 45 | 46 | ```svelte 47 | 56 | 57 | {$post?.title} 58 | ``` -------------------------------------------------------------------------------- /docs/src/pages/guide/patterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Common SvelteFire Patterns 3 | pubDate: 2023-12-23 4 | description: How to implement common patters with Svelte and Firebase 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # Common Patterns 9 | 10 | ## Fetch Data from Firestore for Authenticated Users 11 | 12 | First, Use the `SignedIn` component to access the UID of the current user. Second, pass that UID to the `Doc` or `Collection` component to fetch a Firestore document owned by that user. 13 | 14 | ```svelte 15 | 16 | 17 |

{post.title}

18 |

{post.content}

19 |
20 |
21 | ``` 22 | 23 | ## SSR with SvelteKit 24 | 25 | SvelteFire is a client-side library, but allows you to hydrate server data into a realtime stream. 26 | 27 | First, fetch data from a load function like so: 28 | 29 | ```ts 30 | // +page.ts 31 | import { doc, getDoc } from 'firebase/firestore'; 32 | 33 | export const load = (async () => { 34 | const ref = doc(firestore, 'posts', 'first-post'); 35 | const snapshot = await getDoc(ref); 36 | return { 37 | post: snapshot.data(); 38 | }; 39 | }); 40 | ``` 41 | 42 | Second, pass the server data as the `startWith` value to a store. This will bypass the loading state and ensure the data is rendered in the server HTML, then realtime listeners will be attached afterwards. 43 | 44 | ```svelte 45 | // +page.svelte 46 | 49 | 50 | 51 | 52 |

{post.title}

53 |

{post.content}

54 |
55 | ``` 56 | 57 | Note: This will result in 2 reads from Firestore on initial page load, so only use this pattern when true realtime data necessary. 58 | 59 | ## Dynamic Firestore Queries 60 | 61 | Imagine you have a collection of posts what a user can filter by category. Create a reactive declaration to re-run the query whenever the category changes. 62 | 63 | ```svelte 64 | 74 | 75 | 76 |
    77 | {#each posts as post (post.id)} 78 |
  • {post.content}
  • 79 | {/each} 80 |
81 |
82 | 83 | 84 | ``` 85 | ## Handle File Uploads with Progress Bar 86 | 87 | The example below shows how to upload a file to Firebase Storage and display a progress bar. First, get a file from the user. Second, pass the file to the `UploadTask` component. Third, display the progress bar and download link. 88 | 89 | 90 | ```svelte 91 | 99 | 100 | 101 | 102 | {#if file} 103 | 104 | {#if snapshot?.state === "running" || snapshot?.state === "success"} 105 |

{progress}% uploaded

106 | 107 | {/if} 108 | 109 | {#if snapshot?.state === "error"} 110 | Upload failed 111 | {/if} 112 | 113 | {#if snapshot?.state === "success"} 114 | 115 | {ref?.name} 116 | 117 | {/if} 118 |
119 | {/if} 120 | ``` -------------------------------------------------------------------------------- /docs/src/pages/guide/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Install SvelteFire 3 | pubDate: 2023-07-23 4 | description: How to install SvelteFire 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # QuickStart 9 | 10 | SvelteFire works in both SvelteKit and standalone Svelte apps. This guide assumes you're using SvelteKit. 11 | 12 | 13 | ### 1. Install 14 | 15 | ``` 16 | npm i sveltefire firebase 17 | ``` 18 | 19 | ### 2. Initialize 20 | 21 | Initialize Firebase and add the `FirebaseApp` component to the component tree. Typically, this is done in the root `+layout.svelte` file to access Firebase on all pages. 22 | 23 | #### +layout.svelte 24 | ```svelte 25 | 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | ### 3. Use Firebase! 43 | 44 | You can use stores to access the current user and Firestore. 45 | 46 | ```svelte 47 | 54 | 55 | {$user?.displayName} 56 | {$post?.content} 57 | ``` 58 | 59 | Or you can use components to more easily pass data around. Notice how slot props like `let:user` and `let:data` allow us to access data from the backend with minimal effort. Here are some common examples. 60 | 61 | ```svelte 62 | 66 | 67 | 68 |

Hello {user.uid}

69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 |

{data.title}

78 |

{data.content}

79 |
80 | 81 | 82 | {#each posts as post} 83 |

{post.title}

84 |

{post.content}

85 | {/each} 86 |
87 | ``` 88 | -------------------------------------------------------------------------------- /docs/src/pages/guide/todo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TODO 3 | pubDate: 2023-07-23 4 | description: This feature is coming soon... 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # Coming Soon 9 | 10 | This feature is under development. Please check back later. -------------------------------------------------------------------------------- /docs/src/pages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SvelteFire? 3 | pubDate: 2023-07-23 4 | description: 'Why use SvelteFire over vanilla Firebase?' 5 | layout: ../layouts/MainLayout.astro 6 | --- 7 | 8 | # 🔥 SvelteFire 9 | 10 | SvelteFire is a minimal, yet powerful library that puts realtime Firebase data into Svelte stores. 11 | 12 | ## Why? 13 | 14 | Firebase realtime APIs are callback-based, but we can dramatically improve the developer experience by leveraging Svelte's reactive stores. 15 | 16 | - Access users and realtime Firestore data as Svelte stores 17 | - Automatic subscription disposal to prevent duplicate reads 18 | - Better TypeScript experience for Firebase 19 | - Handle complex relational data between Auth and Firestore 20 | - Easily hydrate SvelteKit server data into a realtime Firebase stream 21 | - Simple Google Analytics integration for SvelteKit 22 | 23 | ## Store Example 24 | 25 | Get the current user: 26 | 27 | ```svelte 28 | 31 | 32 | Hello {$user.uid} 33 | ``` 34 | 35 | Get a Firestore document. Any changes to the document will be reflected instantly: 36 | 37 | ```svelte 38 | 41 | 42 | {$post.title} 43 | {$post.content} 44 | ``` 45 | 46 | ## Component Example 47 | 48 | We can take this a step further with components and slot props. Under the hood, these components use the same stores as above, but make common patterns dead simple. The example below renders content for the signed-in user while fetching multiple levels of relational data `user->post->comments`. 49 | 50 | ```svelte 51 | 52 | 53 | 54 | 55 | 56 | 57 |

Howdy, {user.uid}

58 | 59 | 60 | 61 | 62 |

{post.title}

63 | 64 | 65 | 66 | {#each comments as comment} 67 | 68 | {/each} 69 | ... 70 | ``` -------------------------------------------------------------------------------- /docs/src/pages/rtdb/node-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Node Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire Node Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # Node 9 | 10 | The `Node` component is a wrapper around the `nodeStore`. It renders the node data and handles the loading state. 11 | 12 | ### Props 13 | 14 | - `path` - RealtimeDB path string (e.g. `posts/hi-mom`) 15 | - `startWith` - (optional) initial value to use before the data is fetched 16 | 17 | ### Slots 18 | 19 | - `default` - The node data 20 | - `loading` - Loading state 21 | 22 | ### Slot Props 23 | 24 | - `data` - The node data 25 | - `path` - The Database reference 26 | - `rtdb` - The Database instance 27 | 28 | ### Example 29 | 30 | ```svelte 31 | 34 | 35 | 36 |

{data?.title}

37 | 38 |

Loading...

39 |
40 | ``` -------------------------------------------------------------------------------- /docs/src/pages/rtdb/node-list-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NodeList Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire NodeList Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # NodeList 9 | 10 | The `NodeList` component is a wrapper around the `nodeListStore`. It renders the node list data and handles the loading state. 11 | 12 | ### Props 13 | 14 | - `path` - RealtimeDB reference 15 | - `startWith` - (optional) initial value to use before the collection is fetched 16 | 17 | ### Slots 18 | 19 | - `default` - The node list data 20 | - `loading` - Loading state 21 | 22 | ### Slot Props 23 | 24 | - `data` - An array of nodes 25 | - `ref` - The Database node reference 26 | - `rtdb` - The Database instance 27 | - `count` - The number of nodes returned by the query 28 | 29 | ### Example 30 | 31 | ```svelte 32 | 35 | 36 | 37 | 38 |

Found {count} posts

39 | 40 | {#each data as post} 41 |

{post.title}

42 | {/each} 43 | 44 |

Loading...

45 |
46 | ``` -------------------------------------------------------------------------------- /docs/src/pages/rtdb/node-list-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: nodeListStore 3 | pubDate: 2023-11-23 4 | description: SvelteFire nodeStore API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # nodeListStore 9 | 10 | Subscribes to RealtimeDB node list data and listens to real-time updates. 11 | 12 | ### Parameters 13 | 14 | - `rtdb` - RealtimeDB instance 15 | - `path` - A RealtimeDB path string (e.g. `posts`) 16 | - `startWith` - (optional) initial value to use before the data is fetched 17 | 18 | ### Example 19 | 20 | ```svelte 21 | 27 | 28 | {#each $posts as post} 29 |

{post.title}

30 | {/each} 31 | ``` -------------------------------------------------------------------------------- /docs/src/pages/rtdb/node-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: nodeStore 3 | pubDate: 2023-11-23 4 | description: SvelteFire nodeStore API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # nodeStore 9 | 10 | Subscribes to RealtimeDB node and listens to realtime updates. 11 | 12 | ### Parameters 13 | 14 | - `rtdb` - RealtimeDB instance 15 | - `path` - A RealtimeDB path string (e.g. `posts/hi-mom`) 16 | - `startWith` - (optional) initial value to use before the data is fetched 17 | 18 | ### Example 19 | 20 | ```svelte 21 | 27 | 28 | {$post?.title} 29 | ``` -------------------------------------------------------------------------------- /docs/src/pages/storage/download-url.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DownloadURL Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire DownloadURL Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # DownloadURL 9 | 10 | Returns the download URL for a file in Firebase Storage. 11 | 12 | ### Props 13 | 14 | - `ref` - A Firebase Storage reference or path string (e.g. `files/hi-mom.txt`) 15 | 16 | ### Slots 17 | 18 | - `default` - Shown when the url is available 19 | - `loading` - Shown while the url is loading 20 | 21 | ### Slot Props 22 | 23 | - `link` - The download URL 24 | - `ref` - Storage reference 25 | - `storage` - The Firebase Storage instance 26 | 27 | ### Example 28 | 29 | ```svelte 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {ref?.name} 41 | 42 | ``` -------------------------------------------------------------------------------- /docs/src/pages/storage/storage-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: StorageList Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire StorageList Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # StorageList 9 | 10 | Returns a list of files stored in Firebase Storage. 11 | 12 | ### Props 13 | 14 | - `ref` - A Firebase Storage reference or path string (e.g. `files/hi-mom.txt`) 15 | 16 | ### Slots 17 | 18 | - `default` - Shown when the list is available 19 | - `loading` - Shown while the list is loading 20 | 21 | ### Slot Props 22 | 23 | - `list` - The list of files and prefixes 24 | - `ref` - Storage reference 25 | - `storage` - The Firebase Storage instance 26 | 27 | ### Example 28 | 29 | ```svelte 30 | 33 | 34 | 35 | 36 |
    37 | {#if list === null} 38 |
  • Loading...
  • 39 | {:else if list.prefixes.length === 0 && list.items.length === 0} 40 |
  • Empty
  • 41 | {:else} 42 | 43 | {#each list.prefixes as prefix} 44 |
  • 45 | {prefix.name} 46 |
  • 47 | {/each} 48 | 49 | {#each list.items as item} 50 |
  • 51 | {item.name} 52 |
  • 53 | {/each} 54 | {/if} 55 |
56 |
57 | ``` -------------------------------------------------------------------------------- /docs/src/pages/storage/upload-task.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: UploadTask Component 3 | pubDate: 2023-07-23 4 | description: SvelteFire UploadTask Component API reference 5 | layout: ../../layouts/MainLayout.astro 6 | --- 7 | 8 | # UploadTask 9 | 10 | Uploads a file to a Firebase storage bucket. 11 | 12 | ### Props 13 | 14 | - `ref` - A Firebase Storage reference or path string (e.g. `files/hi-mom.txt`) 15 | - `data` - the file data to be uploaded as `Blob | Uint8Array | ArrayBuffer` 16 | - `metadata` - (optional) file metadata 17 | 18 | 19 | ### Slots 20 | 21 | - `default` 22 | 23 | ### Slot Props 24 | 25 | - `snapshot` - Firebase UploadTaskSnapshot 26 | - `task` - Firebase UploadTask 27 | - `progress` - Number as a percentage of the upload progress 28 | - `storage` - The Firebase Storage instance 29 | 30 | ### Example 31 | 32 | ```svelte 33 | 42 | 43 | 44 | 45 | {#if file} 46 | 47 | {#if snapshot?.state === "running"} 48 | {progress}% uploaded 49 | {/if} 50 | 51 | {#if snapshot?.state === "success"} 52 | 53 | Link 54 | 55 | {/if} 56 | 57 | {/if} 58 | ``` -------------------------------------------------------------------------------- /docs/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const colors = require("tailwindcss/colors"); 3 | 4 | module.exports = { 5 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 6 | theme: { 7 | extend: {}, 8 | fontFamily: { 9 | sans: ["Overpass", "sans-serif"], 10 | body: ["Overpass", "sans-serif"], 11 | code: ["Fira Mono", "sans-serif"], 12 | }, 13 | colors: { 14 | transparent: "transparent", 15 | current: "currentColor", 16 | white: "#ffffff", 17 | black: "#000000", 18 | gray1: "#f8f8f8", 19 | gray2: "#dbe1e8", 20 | gray3: "#b2becd", 21 | gray4: "#6c7983", 22 | gray5: "#454e56", 23 | gray6: "#2a2e35", 24 | gray7: "#12181b", 25 | link: "#0000ee", 26 | blue: colors.blue, 27 | green: colors.green, 28 | pink: colors.pink, 29 | purple: colors.purple, 30 | orange: colors.orange, 31 | red: colors.red, 32 | yellow: colors.yellow, 33 | }, 34 | extend: { 35 | boxShadow: { 36 | "3xl": "0 5px 20px rgb(0 0 0 / 30%)", 37 | "4xl": "0 5px 20px rgb(0 0 0 / 90%)", 38 | }, 39 | typography: { 40 | DEFAULT: { 41 | css: { 42 | h1: { 43 | "font-weight": "bold", 44 | "font-size": "2.5rem", 45 | "overflow-wrap": "break-word" 46 | }, 47 | h2: { 48 | "font-weight": "bold", 49 | "font-size": "2rem", 50 | "overflow-wrap": "break-word" 51 | }, 52 | h3: { 53 | "font-weight": "normal", 54 | "font-size": "1.5rem", 55 | "overflow-wrap": "break-word" 56 | }, 57 | h4: { 58 | "font-weight": "normal", 59 | "font-size": "1.5rem", 60 | }, 61 | h5: { 62 | "font-weight": "normal", 63 | "font-size": "1.25rem", 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | plugins: [require('@tailwindcss/typography')], 71 | darkMode: "class", 72 | }; 73 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base" 3 | } -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "auth": { 4 | "port": 9099 5 | }, 6 | "firestore": { 7 | "port": 8080 8 | }, 9 | "database": { 10 | "port": 9000 11 | }, 12 | "storage": { 13 | "port": 9199 14 | }, 15 | "hosting": { 16 | "port": 5000 17 | }, 18 | "ui": { 19 | "enabled": true 20 | }, 21 | "singleProjectMode": true 22 | }, 23 | "storage": { 24 | "rules": "storage.rules" 25 | }, 26 | "hosting": { 27 | "public": "docs/dist", 28 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltefire", 3 | "version": "0.4.5", 4 | "scripts": { 5 | "dev": "vite dev", 6 | "build": "vite build && npm run package", 7 | "preview": "vite preview", 8 | "package": "svelte-kit sync && svelte-package && publint", 9 | "prepublishOnly": "npm run package", 10 | "test": "playwright test", 11 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 | "lint": "eslint .", 14 | "fire": "firebase emulators:start" 15 | }, 16 | "exports": { 17 | ".": { 18 | "types": "./dist/index.d.ts", 19 | "svelte": "./dist/index.js" 20 | } 21 | }, 22 | "files": [ 23 | "dist", 24 | "!dist/**/*.test.*", 25 | "!dist/**/*.spec.*" 26 | ], 27 | "peerDependencies": { 28 | "firebase": "^9.0.0", 29 | "svelte": "^4.0.0" 30 | }, 31 | "devDependencies": { 32 | "@playwright/test": "^1.28.1", 33 | "@sveltejs/adapter-auto": "^2.0.0", 34 | "@sveltejs/kit": "^1.20.4", 35 | "@sveltejs/package": "^2.0.0", 36 | "@typescript-eslint/eslint-plugin": "^5.45.0", 37 | "@typescript-eslint/parser": "^5.45.0", 38 | "eslint": "^8.28.0", 39 | "eslint-plugin-svelte": "^2.30.0", 40 | "firebase-tools": "^12.4.5", 41 | "publint": "^0.1.9", 42 | "svelte": "^4.0.5", 43 | "svelte-check": "^3.4.3", 44 | "tslib": "^2.4.1", 45 | "typescript": "^5.0.0", 46 | "vite": "^4.4.2" 47 | }, 48 | "svelte": "./dist/index.js", 49 | "types": "./dist/index.d.ts", 50 | "type": "module" 51 | } 52 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@playwright/test'; 2 | 3 | export default defineConfig({ 4 | webServer: { 5 | command: 'firebase emulators:exec --only firestore,database,storage,auth "npm run build && npm run preview"', 6 | port: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }); 11 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/Collection.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | {#if $store !== undefined} 30 | 31 | {:else} 32 | 33 | {/if} 34 | -------------------------------------------------------------------------------- /src/lib/components/Doc.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | {#if $store !== undefined && $store !== null} 28 | 29 | {:else} 30 | 31 | {/if} 32 | -------------------------------------------------------------------------------- /src/lib/components/DownloadURL.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | {#if $store !== undefined} 18 | 19 | {:else} 20 | 21 | {/if} 22 | 23 | -------------------------------------------------------------------------------- /src/lib/components/FirebaseApp.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/lib/components/Node.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | {#if $store !== undefined} 19 | 20 | {:else} 21 | 22 | {/if} 23 | -------------------------------------------------------------------------------- /src/lib/components/NodeList.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | {#if $store !== undefined} 24 | 25 | {:else} 26 | 27 | {/if} 28 | -------------------------------------------------------------------------------- /src/lib/components/PageView.svelte: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /src/lib/components/SignedIn.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#if $user} 16 | signOut(auth)} /> 17 | {/if} -------------------------------------------------------------------------------- /src/lib/components/SignedOut.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#if !$user} 16 | 17 | {/if} -------------------------------------------------------------------------------- /src/lib/components/StorageList.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | {#if $listStore !== undefined} 18 | 19 | {:else} 20 | 21 | {/if} 22 | 23 | -------------------------------------------------------------------------------- /src/lib/components/UploadTask.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | {#if $upload !== undefined} 33 | 34 | {/if} 35 | -------------------------------------------------------------------------------- /src/lib/components/User.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#if $user} 16 | 17 | {:else} 18 | 19 | {/if} -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | // Reexport your entry components here 2 | import User from './components/User.svelte'; 3 | import Collection from './components/Collection.svelte'; 4 | import Doc from './components/Doc.svelte'; 5 | import FirebaseApp from './components/FirebaseApp.svelte'; 6 | import SignedIn from './components/SignedIn.svelte'; 7 | import SignedOut from './components/SignedOut.svelte'; 8 | import DownloadURL from './components/DownloadURL.svelte'; 9 | import StorageList from './components/StorageList.svelte'; 10 | import UploadTask from './components/UploadTask.svelte'; 11 | import PageView from './components/PageView.svelte'; 12 | import { userStore } from './stores/auth'; 13 | import { docStore, collectionStore } from './stores/firestore'; 14 | import { nodeStore, nodeListStore } from './stores/rtdb'; 15 | import { getFirebaseContext } from './stores/sdk'; 16 | import { downloadUrlStore, storageListStore, uploadTaskStore } from './stores/storage'; 17 | 18 | export { 19 | Doc, 20 | User, 21 | Collection, 22 | FirebaseApp, 23 | SignedOut, 24 | SignedIn, 25 | UploadTask, 26 | StorageList, 27 | DownloadURL, 28 | PageView, 29 | downloadUrlStore, 30 | storageListStore, 31 | uploadTaskStore, 32 | docStore, 33 | collectionStore, 34 | nodeStore, 35 | nodeListStore, 36 | userStore, 37 | getFirebaseContext, 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/stores/auth.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | import { onAuthStateChanged, type Auth } from "firebase/auth"; 3 | 4 | /** 5 | * @param {Auth} auth firebase auth instance 6 | * @param {any} startWith optional default data. Useful for server-side cookie-based auth 7 | * @returns a store with the current firebase user 8 | */ 9 | export function userStore(auth: Auth, startWith = null) { 10 | let unsubscribe: () => void; 11 | 12 | // Fallback for SSR 13 | if (!globalThis.window) { 14 | const { subscribe } = writable(startWith); 15 | return { 16 | subscribe, 17 | }; 18 | } 19 | 20 | // Fallback for missing SDK 21 | if (!auth) { 22 | console.warn( 23 | "Firebase Auth is not initialized. Are you missing FirebaseApp as a parent component?" 24 | ); 25 | const { subscribe } = writable(null); 26 | return { 27 | subscribe, 28 | }; 29 | } 30 | 31 | const { subscribe } = writable(auth?.currentUser ?? null, (set) => { 32 | unsubscribe = onAuthStateChanged(auth, (user) => { 33 | set(user); 34 | }); 35 | 36 | return () => unsubscribe(); 37 | }); 38 | 39 | return { 40 | subscribe, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/stores/firestore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | import { doc, collection, onSnapshot } from "firebase/firestore"; 3 | import type { 4 | Query, 5 | CollectionReference, 6 | DocumentReference, 7 | Firestore, 8 | } from "firebase/firestore"; 9 | 10 | interface DocStore { 11 | subscribe: (cb: (value: T | null) => void) => void | (() => void); 12 | ref: DocumentReference | null; 13 | id: string; 14 | } 15 | 16 | /** 17 | * @param {Firestore} firestore firebase firestore instance 18 | * @param {string|DocumentReference} ref document path or reference 19 | * @param {T} startWith optional default data 20 | * @returns a store with realtime updates on document data 21 | */ 22 | export function docStore( 23 | firestore: Firestore, 24 | ref: string | DocumentReference, 25 | startWith?: T 26 | ): DocStore { 27 | let unsubscribe: () => void; 28 | 29 | // Fallback for SSR 30 | if (!globalThis.window) { 31 | const { subscribe } = writable(startWith); 32 | return { 33 | subscribe, 34 | ref: null, 35 | id: "", 36 | }; 37 | } 38 | 39 | // Fallback for missing SDK 40 | if (!firestore) { 41 | console.warn( 42 | "Firestore is not initialized. Are you missing FirebaseApp as a parent component?" 43 | ); 44 | const { subscribe } = writable(null); 45 | return { 46 | subscribe, 47 | ref: null, 48 | id: "", 49 | }; 50 | } 51 | 52 | const docRef = 53 | typeof ref === "string" 54 | ? (doc(firestore, ref) as DocumentReference) 55 | : ref; 56 | 57 | const { subscribe } = writable(startWith, (set) => { 58 | unsubscribe = onSnapshot(docRef, (snapshot) => { 59 | set((snapshot.data() as T) ?? null); 60 | }); 61 | 62 | return () => unsubscribe(); 63 | }); 64 | 65 | return { 66 | subscribe, 67 | ref: docRef, 68 | id: docRef.id, 69 | }; 70 | } 71 | 72 | interface CollectionStore { 73 | subscribe: (cb: (value: T | []) => void) => void | (() => void); 74 | ref: CollectionReference | Query | null; 75 | } 76 | 77 | /** 78 | * @param {Firestore} firestore firebase firestore instance 79 | * @param {string|Query|CollectionReference} ref collection path, reference, or query 80 | * @param {[]} startWith optional default data 81 | * @returns a store with realtime updates on collection data 82 | */ 83 | export function collectionStore( 84 | firestore: Firestore, 85 | ref: string | Query | CollectionReference, 86 | startWith: T[] = [] 87 | ): CollectionStore { 88 | let unsubscribe: () => void; 89 | 90 | // Fallback for SSR 91 | if (!globalThis.window) { 92 | const { subscribe } = writable(startWith); 93 | return { 94 | subscribe, 95 | ref: null, 96 | }; 97 | } 98 | 99 | // Fallback for missing SDK 100 | if (!firestore) { 101 | console.warn( 102 | "Firestore is not initialized. Are you missing FirebaseApp as a parent component?" 103 | ); 104 | const { subscribe } = writable([]); 105 | return { 106 | subscribe, 107 | ref: null, 108 | }; 109 | } 110 | 111 | const colRef = typeof ref === "string" ? collection(firestore, ref) : ref; 112 | 113 | const { subscribe } = writable(startWith, (set) => { 114 | unsubscribe = onSnapshot(colRef, (snapshot) => { 115 | const data = snapshot.docs.map((s) => { 116 | return { id: s.id, ref: s.ref, ...s.data() } as T; 117 | }); 118 | set(data); 119 | }); 120 | 121 | return () => unsubscribe(); 122 | }); 123 | 124 | return { 125 | subscribe, 126 | ref: colRef, 127 | }; 128 | } 129 | -------------------------------------------------------------------------------- /src/lib/stores/rtdb.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | import { onValue, ref as dbRef } from "firebase/database"; 3 | import type { Database } from "firebase/database"; 4 | 5 | /** 6 | * @param {Database} rtdb - Firebase Realtime Database instance. 7 | * @param {string} path - Path to the individual database node. 8 | * @param {T | undefined} startWith - Optional default data. 9 | * @returns a store with realtime updates on individual database nodes. 10 | */ 11 | export function nodeStore( 12 | rtdb: Database, 13 | path: string, 14 | startWith?: T 15 | ) { 16 | const dataRef = dbRef(rtdb, path); 17 | 18 | const { subscribe } = writable(startWith, (set) => { 19 | const unsubscribe = onValue(dataRef, (snapshot) => { 20 | set(snapshot.val() as T); 21 | }); 22 | 23 | return unsubscribe; 24 | }); 25 | 26 | return { 27 | subscribe, 28 | ref: dataRef, 29 | }; 30 | } 31 | 32 | /** 33 | * @param {Database} rtdb - Firebase Realtime Database instance. 34 | * @param {string} path - Path to the list of nodes. 35 | * @param {T[]} startWith - Optional default data. 36 | * @returns a store with realtime updates on lists of nodes. 37 | */ 38 | export function nodeListStore( 39 | rtdb: Database, 40 | path: string, 41 | startWith: T[] = [] 42 | ) { 43 | const listRef = dbRef(rtdb, path); 44 | 45 | const { subscribe } = writable(startWith, (set) => { 46 | const unsubscribe = onValue(listRef, (snapshot) => { 47 | const dataArr: T[] = []; 48 | snapshot.forEach((childSnapshot) => { 49 | const childData = childSnapshot.val(); 50 | dataArr.push({ 51 | nodeKey: childSnapshot.ref.key, 52 | ...(typeof childData === "object" ? childData : {}), 53 | } as T); 54 | }); 55 | set(dataArr); 56 | }); 57 | 58 | return unsubscribe; 59 | }); 60 | 61 | return { 62 | subscribe, 63 | ref: listRef, 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/stores/sdk.ts: -------------------------------------------------------------------------------- 1 | import type { Firestore } from "firebase/firestore"; 2 | import type { Database } from "firebase/database"; 3 | import type { Auth } from "firebase/auth"; 4 | import { getContext, setContext } from "svelte"; 5 | import type { FirebaseStorage } from "firebase/storage"; 6 | import type { Analytics } from "firebase/analytics"; 7 | 8 | export interface FirebaseSDKContext { 9 | auth?: Auth; 10 | firestore?: Firestore; 11 | rtdb?: Database; 12 | storage?: FirebaseStorage; 13 | analytics?: Analytics | null; 14 | } 15 | 16 | export const contextKey = "firebase"; 17 | 18 | export function setFirebaseContext(sdks: FirebaseSDKContext) { 19 | setContext(contextKey, sdks); 20 | } 21 | 22 | /** 23 | * Get the Firebase SDKs from Svelte context 24 | */ 25 | export function getFirebaseContext(): FirebaseSDKContext { 26 | return getContext(contextKey); 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/stores/storage.ts: -------------------------------------------------------------------------------- 1 | import { readable } from "svelte/store"; 2 | import { 3 | getDownloadURL, 4 | list, 5 | ref, 6 | uploadBytesResumable, 7 | } from "firebase/storage"; 8 | 9 | import type { 10 | StorageReference, 11 | FirebaseStorage, 12 | ListResult, 13 | UploadTaskSnapshot, 14 | UploadMetadata, 15 | } from "firebase/storage"; 16 | 17 | const defaultListResult: ListResult = { 18 | prefixes: [], 19 | items: [], 20 | }; 21 | 22 | interface StorageListStore { 23 | subscribe: (cb: (value: ListResult) => void) => void | (() => void); 24 | reference: StorageReference | null; 25 | } 26 | 27 | /** 28 | * @param {FirebaseStorage} storage firebase storage instance 29 | * @param {string|StorageReference} reference file or storage item path or reference 30 | * @param {{prefixes:[], items:[]}} startWith optional default data 31 | * @returns a store with the list result 32 | */ 33 | export function storageListStore( 34 | storage: FirebaseStorage, 35 | reference: string | StorageReference, 36 | startWith: ListResult = defaultListResult 37 | ): StorageListStore { 38 | // Fallback for SSR 39 | if (!globalThis.window) { 40 | const { subscribe } = readable(startWith); 41 | return { 42 | subscribe, 43 | reference: null, 44 | }; 45 | } 46 | 47 | // Fallback for missing SDK 48 | if (!storage) { 49 | console.warn( 50 | "Cloud Storage is not initialized. Are you missing FirebaseApp as a parent component?" 51 | ); 52 | const { subscribe } = readable(defaultListResult); 53 | return { 54 | subscribe, 55 | reference: null, 56 | }; 57 | } 58 | 59 | const storageRef = 60 | typeof reference === "string" ? ref(storage, reference) : reference; 61 | 62 | const { subscribe } = readable(startWith, (set) => { 63 | list(storageRef).then((snapshot) => { 64 | set(snapshot); 65 | }); 66 | }); 67 | 68 | return { 69 | subscribe, 70 | reference: storageRef, 71 | }; 72 | } 73 | 74 | interface DownloadUrlStore { 75 | subscribe: (cb: (value: string | null) => void) => void | (() => void); 76 | reference: StorageReference | null; 77 | } 78 | 79 | /** 80 | * @param {FirebaseStorage} storage firebase storage instance 81 | * @param {string|StorageReference} reference file or storage item path or reference 82 | * @param {null} startWith optional default data 83 | * @returns a store with the list result 84 | */ 85 | export function downloadUrlStore( 86 | storage: FirebaseStorage, 87 | reference: string | StorageReference, 88 | startWith: string | null = null 89 | ): DownloadUrlStore { 90 | // Fallback for SSR 91 | if (!globalThis.window) { 92 | const { subscribe } = readable(startWith); 93 | return { 94 | subscribe, 95 | reference: null, 96 | }; 97 | } 98 | 99 | // Fallback for missing SDK 100 | if (!storage) { 101 | console.warn( 102 | "Cloud Storage is not initialized. Are you missing FirebaseApp as a parent component?" 103 | ); 104 | const { subscribe } = readable(null); 105 | return { 106 | subscribe, 107 | reference: null, 108 | }; 109 | } 110 | 111 | const storageRef = 112 | typeof reference === "string" ? ref(storage, reference) : reference; 113 | 114 | const { subscribe } = readable(startWith, (set) => { 115 | getDownloadURL(storageRef).then((snapshot) => { 116 | set(snapshot); 117 | }); 118 | }); 119 | 120 | return { 121 | subscribe, 122 | reference: storageRef, 123 | }; 124 | } 125 | 126 | interface UploadTaskStore { 127 | subscribe: ( 128 | cb: (value: UploadTaskSnapshot | null) => void 129 | ) => void | (() => void); 130 | reference: StorageReference | null; 131 | } 132 | 133 | export function uploadTaskStore( 134 | storage: FirebaseStorage, 135 | reference: string | StorageReference, 136 | data: Blob | Uint8Array | ArrayBuffer, 137 | metadata?: UploadMetadata | undefined 138 | ): UploadTaskStore { 139 | // Fallback for SSR 140 | if (!globalThis.window) { 141 | const { subscribe } = readable(null); 142 | return { 143 | subscribe, 144 | reference: null, 145 | }; 146 | } 147 | 148 | // Fallback for missing SDK 149 | if (!storage) { 150 | console.warn( 151 | "Cloud Storage is not initialized. Are you missing FirebaseApp as a parent component?" 152 | ); 153 | const { subscribe } = readable(null); 154 | return { 155 | subscribe, 156 | reference: null, 157 | }; 158 | } 159 | 160 | const storageRef = 161 | typeof reference === "string" ? ref(storage, reference) : reference; 162 | 163 | let unsubscribe: () => void; 164 | 165 | const { subscribe } = readable(null, (set) => { 166 | const task = uploadBytesResumable(storageRef, data, metadata); 167 | unsubscribe = task.on( 168 | "state_changed", 169 | (snapshot) => { 170 | set(snapshot); 171 | }, 172 | (error) => { 173 | console.error(error); 174 | set(task.snapshot); 175 | }, 176 | () => { 177 | set(task.snapshot); 178 | } 179 | ); 180 | return () => unsubscribe(); 181 | }); 182 | 183 | return { 184 | subscribe, 185 | reference: storageRef, 186 | }; 187 | } 188 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Welcome to SvelteFire

8 | 9 | 17 |
    18 |
  • Auth Context: {!!ctx.auth}
  • 19 |
  • Firestore Context: {!!ctx.firestore}
  • 20 |
  • Realtime Database Context: {!!ctx.rtdb}
  • 21 |
  • Storage Context: {!!ctx.storage}
  • 22 |
23 | -------------------------------------------------------------------------------- /src/routes/analytics-test/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 |

Analytics Layout

9 | {#key $page.route.id} 10 |

Logged analytics for {$page.route.id}

11 | 12 | {/key} 13 | -------------------------------------------------------------------------------- /src/routes/analytics-test/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad } from './$types'; 2 | 3 | export const load: PageServerLoad = (async () => { 4 | return { 5 | title: 'Analytics Test 1 - Server Rendered', 6 | }; 7 | }) satisfies PageServerLoad; -------------------------------------------------------------------------------- /src/routes/analytics-test/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | {data.title} 10 | 11 | 12 |

{data.title}

13 | 14 |

15 | This is a test page for analytics. The title is always server rendered. 16 |

17 | 18 | Analytics CSR Test 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/routes/analytics-test/client-side/+page.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | {title} 13 | 14 | 15 | 16 | 17 |

{title}

18 | 19 |

20 | This is a test page for analytics. The title is always server rendered. 21 |

22 | 23 | Analytics SSR Test 24 | 25 | -------------------------------------------------------------------------------- /src/routes/auth-test/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Auth Test

8 | 9 | 10 |

Signed In

11 |

Hello {user.uid}

12 | 13 |
14 | 15 | 16 |

Signed Out

17 | 18 |
19 | -------------------------------------------------------------------------------- /src/routes/firebase.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp } from "firebase/app"; 2 | import { 3 | connectFirestoreEmulator, 4 | doc, 5 | getFirestore, 6 | setDoc, 7 | } from "firebase/firestore"; 8 | import { 9 | connectDatabaseEmulator, 10 | getDatabase, 11 | ref as dbRef, 12 | set, 13 | } from "firebase/database"; 14 | import { connectAuthEmulator, getAuth } from "firebase/auth"; 15 | import { dev } from "$app/environment"; 16 | import { 17 | connectStorageEmulator, 18 | getStorage, 19 | ref as storageRef, 20 | uploadString, 21 | } from "firebase/storage"; 22 | import { getAnalytics } from "firebase/analytics"; 23 | 24 | const firebaseConfig = { 25 | apiKey: "AIzaSyAMHfJp1ec85QBo-mnke89qtiYGen9zTSE", 26 | authDomain: "sveltefire-testing.firebaseapp.com", 27 | databaseURL: "https://sveltefire-testing.firebaseio.com", 28 | projectId: "sveltefire-testing", 29 | storageBucket: "sveltefire-testing.appspot.com", 30 | messagingSenderId: "1030648105982", 31 | appId: "1:1030648105982:web:2afebc34841fa242ed4eaf", 32 | measurementId: "G-RT6GM89V6K" 33 | }; 34 | 35 | // Initialize Firebase 36 | export const app = initializeApp(firebaseConfig); 37 | export const db = getFirestore(app); 38 | export const rtdb = getDatabase(app); 39 | export const auth = getAuth(app); 40 | export const storage = getStorage(app); 41 | export const analytics = typeof window !== "undefined" ? getAnalytics(app) : null; 42 | 43 | if (dev || import.meta.env.MODE === "ci") { 44 | connectAuthEmulator(auth, "http://localhost:9099"); 45 | connectFirestoreEmulator(db, "localhost", 8080); 46 | connectDatabaseEmulator(rtdb, "localhost", 9000); 47 | connectStorageEmulator(storage, "localhost", 9199); 48 | 49 | // Seed Firestore 50 | setDoc(doc(db, "posts", "test"), { 51 | title: "Hi Mom", 52 | content: "this is a test", 53 | }); 54 | 55 | // Seed Realtime Database 56 | set(dbRef(rtdb, "posts/test"), { 57 | title: "Hi Mom", 58 | content: "this is a test", 59 | }); 60 | 61 | // Create a reference to the file to create 62 | const fileRef = storageRef(storage, "test.txt"); 63 | 64 | // Upload a string to the file 65 | uploadString(fileRef, "Hello, world!", "raw") 66 | .then(() => { 67 | console.log("File created successfully!"); 68 | }) 69 | .catch((error) => { 70 | console.error("Error creating file:", error); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /src/routes/firestore-test/+page.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 |

Firestore Test

35 | 36 |

Single Document

37 | 38 | 39 |

{post?.title}

40 |
41 |

Loading...

42 |
43 |
44 | 45 |

User Owned Posts

46 | 47 | 48 |

Signed Out

49 | 50 |
51 | 52 | 53 | 54 | 55 |

Collection

56 | 62 |

You've made {count} posts

63 | 64 |
    65 | {#each posts as post (post.id)} 66 |
  • {post?.content} ... {post.id}
  • 67 | {/each} 68 |
69 | 70 | 71 |
72 |
73 | -------------------------------------------------------------------------------- /src/routes/rtdb-test/+page.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |

Realtime Database Test

22 | 23 |

Single Data Reference

24 | 25 | 26 |

{post?.title}

27 |
28 |

Loading...

29 |
30 |
31 | 32 |

User Owned Data

33 | 34 | 35 |

Signed Out

36 | 37 |
38 | 39 | 40 |

Data List

41 | 47 |

You've made {count} posts

48 | 49 |
    50 | {#each posts as post (post.nodeKey)} 51 |
  • {post?.content} ... {post.nodeKey}
  • 52 | {/each} 53 |
54 | 55 | 56 |
57 |
58 | -------------------------------------------------------------------------------- /src/routes/ssr-test/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Not Hydrated: {data?.title}

8 | 9 | 10 |

Hydrated: {realtimeData?.title}

11 |
12 | 13 |

14 | Home 15 |

-------------------------------------------------------------------------------- /src/routes/ssr-test/+page.ts: -------------------------------------------------------------------------------- 1 | import { doc, getDoc } from 'firebase/firestore'; 2 | import { db } from '../firebase.js'; 3 | 4 | export const load = (async () => { 5 | const ref = doc(db, 'posts', 'test'); 6 | const docSnap = await getDoc(ref); 7 | const data = docSnap.data(); 8 | return { 9 | title: data?.title, 10 | content: data?.content, 11 | uid: data?.uid, 12 | }; 13 | }); -------------------------------------------------------------------------------- /src/routes/storage-test/+page.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |

Storage Test

18 | 19 | 20 |
    21 | {#if list === null} 22 |
  • Loading...
  • 23 | {:else if list.prefixes.length === 0 && list.items.length === 0} 24 |
  • Empty
  • 25 | {:else} 26 | {#each list.items as item} 27 |
  • 28 | 29 | {ref?.name} 30 | 31 |
  • 32 | {/each} 33 | {/if} 34 |
35 |
36 | 37 |

Upload Task

38 | 39 | 40 | 41 | 42 | {#if file} 43 | 44 | {#if snapshot?.state === "running" || snapshot?.state === "success"} 45 |

{progress}% uploaded

46 | {/if} 47 | 48 | {#if snapshot?.state === "error"} 49 | Upload failed 50 | {/if} 51 | 52 | {#if snapshot?.state === "success"} 53 | 54 | {ref?.name} 55 | 56 | {/if} 57 |
58 | {/if} 59 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codediodeio/sveltefire/b78f52dce8e188fb87d9494fb7631150cc5139b7/static/favicon.png -------------------------------------------------------------------------------- /storage.rules: -------------------------------------------------------------------------------- 1 | // Firebase Storage Rules allow you to define how your data should be structured and when data can be read or written. 2 | // See https://firebase.google.com/docs/storage/security/start for more information. 3 | rules_version='2' 4 | // Anyone can read or write to the bucket, even non-users of your app. 5 | // Because it is shared with Google App Engine, this will also make files uploaded via App Engine public. 6 | service firebase.storage { 7 | match /b/{bucket}/o { 8 | match /{allPaths=**} { 9 | allow read, write: if true; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/kit/vite'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: vitePreprocess(), 7 | 8 | kit: { 9 | adapter: adapter() 10 | } 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /tests/auth.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, type Page } from "@playwright/test"; 2 | 3 | test.describe.serial("Auth", () => { 4 | let page: Page; 5 | test.beforeAll(async ({ browser }) => { 6 | page = await browser.newPage(); 7 | await page.goto("/auth-test"); 8 | }); 9 | test.afterAll(async () => { 10 | await page.close(); 11 | }); 12 | 13 | test("Renders UI conditionally based on auth state", async () => { 14 | await expect(page.getByText("Signed Out")).toBeVisible(); 15 | await expect(page.getByText("Signed In")).toBeHidden(); 16 | }); 17 | 18 | test("User can sign in and out", async () => { 19 | 20 | await expect(page.getByRole("button", { name: "Sign In" })).toBeVisible(); 21 | await page.getByRole("button", { name: "Sign In" }).click({delay: 1000}); 22 | await expect(page.getByRole("button", { name: "Sign Out" })).toBeVisible(); 23 | 24 | await page.getByRole("button", { name: "Sign Out" }).click(); 25 | await expect(page.getByRole("button", { name: "Sign In" })).toBeVisible(); 26 | await expect(page.getByText("Signed In")).toBeHidden(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/firestore.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('Renders a single document', async ({ page }) => { 4 | await page.goto('/firestore-test'); 5 | await expect(page.getByTestId('doc-data')).toContainText('Hi Mom'); 6 | }); 7 | 8 | test('Renders a collection of items for an authenticated user in realtime', async ({ page }) => { 9 | await page.goto('/firestore-test'); 10 | await page.getByRole('button', { 'name': 'Sign In'}).click({delay: 1000}); 11 | await expect(page.getByTestId('count')).toContainText('0 posts'); 12 | await page.getByRole('button', { name: 'Add Post' }).click(); 13 | await expect(page.getByTestId('count')).toContainText('1 posts'); 14 | await page.getByRole('button', { name: 'Add Post' }).click(); 15 | await expect(page.getByTestId('count')).toContainText('2 posts'); 16 | await expect(page.locator('li')).toHaveCount(2); 17 | await expect(page.locator('li')).toContainText([ 18 | 'firestore item', 19 | 'firestore item' 20 | ]); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/main.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "@playwright/test"; 2 | 3 | test('SvelteFire app initializes properly', async ({ page }) => { 4 | await page.goto('/'); 5 | await expect(page.locator('h1')).toHaveText('Welcome to SvelteFire'); 6 | }); 7 | 8 | test('Firebase SDK context is defined via FirebaseApp component', async ({ page }) => { 9 | await page.goto('/'); 10 | 11 | await expect( page.getByTestId('auth')).toContainText('true'); 12 | await expect( page.getByTestId('firestore')).toContainText('true'); 13 | await expect( page.getByTestId("rtdb")).toContainText("true"); 14 | await expect( page.getByTestId('storage')).toContainText('true'); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/rtdb.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "@playwright/test"; 2 | 3 | test("Renders a single node", async ({ page }) => { 4 | await page.goto("/rtdb-test"); 5 | await expect(page.getByTestId("node-data")).toContainText("Hi Mom"); 6 | }); 7 | 8 | test("Renders a list of nodes for an authenticated user in realtime", async ({ 9 | page, 10 | }) => { 11 | await page.goto("/rtdb-test"); 12 | 13 | await page.getByRole("button", { name: "Sign In" }).click(); 14 | await expect(page.getByTestId("count")).toContainText("0 posts"); 15 | await page.getByRole("button", { name: "Add Post" }).click(); 16 | await expect(page.getByTestId("count")).toContainText("1 posts"); 17 | await page.getByRole("button", { name: "Add Post" }).click(); 18 | await expect(page.getByTestId("count")).toContainText("2 posts"); 19 | await expect(page.locator("li")).toHaveCount(2); 20 | await expect(page.locator("li")).toContainText(["RTDB item", "RTDB item"]); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/storage.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | 4 | test('Renders download links', async ({ page }) => { 5 | await page.goto('/storage-test'); 6 | await page.waitForSelector('[data-testid="download-link"]'); 7 | const linksCount = await page.getByTestId('download-link').count() 8 | expect( linksCount ).toBeGreaterThan(0); 9 | }); 10 | 11 | test('Uploads a file', async ({ page }) => { 12 | await page.goto('/storage-test'); 13 | await page.getByRole('button', { name: 'Make File' }).click(); 14 | await expect(page.getByTestId('progress')).toContainText('100% uploaded'); 15 | await expect(page.getByTestId('download-link2')).toContainText('test-upload.txt'); 16 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "Bundler" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | --------------------------------------------------------------------------------