├── .env.example ├── .eslintignore ├── .eslintrc.cjs ├── .firebaserc ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── firebase-deploy-docs.yml │ ├── npm-publish.yml │ └── run-tests.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public │ └── favicon.svg ├── src │ ├── assets │ │ └── houston.webp │ ├── content │ │ ├── config.ts │ │ └── docs │ │ │ ├── guides │ │ │ └── getting-started.md │ │ │ ├── index.mdx │ │ │ └── reference │ │ │ ├── App │ │ │ └── supabase-app.md │ │ │ ├── Authentication │ │ │ ├── authenticated.md │ │ │ ├── session-store.md │ │ │ └── unauthenticated.md │ │ │ ├── Database │ │ │ ├── collection.md │ │ │ └── item.md │ │ │ ├── Functions │ │ │ ├── delayed-function-call.md │ │ │ ├── function-call.md │ │ │ ├── interval-function-call.md │ │ │ └── scheduled-function-call.md │ │ │ ├── Realtime │ │ │ ├── broadcast-channel.md │ │ │ ├── db-changes-channel.md │ │ │ └── realtime-presence.md │ │ │ └── Storage │ │ │ ├── bucket-context.md │ │ │ ├── bucket-files-list.md │ │ │ ├── buckets-list.md │ │ │ ├── download-url.md │ │ │ └── uploader.md │ └── env.d.ts └── tsconfig.json ├── firebase.json ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── src ├── app.d.ts ├── app.html ├── index.test.ts ├── lib │ ├── components │ │ ├── SupabaseApp.svelte │ │ ├── auth │ │ │ ├── Authenticated.svelte │ │ │ └── Unauthenticated.svelte │ │ ├── database │ │ │ ├── Collection.svelte │ │ │ └── Item.svelte │ │ ├── function │ │ │ ├── DelayedFunctionCall.svelte │ │ │ ├── FunctionCall.svelte │ │ │ ├── IntervalFunctionCall.svelte │ │ │ └── ScheduledFunctionCall.svelte │ │ ├── realtime │ │ │ ├── BroadcastChannel.svelte │ │ │ ├── DbChanges.svelte │ │ │ └── RealtimePresence.svelte │ │ └── storage │ │ │ ├── BucketContext.svelte │ │ │ ├── BucketFilesList.svelte │ │ │ ├── BucketsList.svelte │ │ │ ├── DownloadURL.svelte │ │ │ └── Uploader.svelte │ ├── index.ts │ └── stores │ │ ├── broadcast-channel.ts │ │ ├── bucket.ts │ │ ├── database.ts │ │ ├── db-changes-channel.ts │ │ ├── function.ts │ │ ├── presence-channel.ts │ │ ├── session.ts │ │ └── supabase-sdk.ts └── routes │ ├── +layout.svelte │ ├── +page.svelte │ ├── auth │ └── +page.svelte │ ├── constants.ts │ ├── database │ └── +page.svelte │ ├── function │ └── +page.svelte │ ├── realtime │ └── +page.svelte │ └── storage │ └── +page.svelte ├── static └── favicon.png ├── supabase ├── .gitignore ├── config.toml ├── functions │ └── get-time │ │ └── index.ts ├── migrations │ ├── 20230915110343_init.sql │ └── 20231004214009_test-table.sql └── seed.sql ├── svelte.config.js ├── tests ├── auth.test.ts ├── buckets.test.ts ├── database.test.ts ├── function.test.ts └── realtime.test.ts ├── tsconfig.json └── vite.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | PUBLIC_SUPABASE_URL= 2 | PUBLIC_SUPABASE_KEY= 3 | PUBLIC_TEST_USER_EMAIL= 4 | PUBLIC_TEST_USER_PASSWORD= -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:svelte/recommended', 7 | 'prettier' 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | plugins: ['@typescript-eslint'], 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020, 14 | extraFileExtensions: ['.svelte'] 15 | }, 16 | env: { 17 | browser: true, 18 | es2017: true, 19 | node: true 20 | }, 21 | overrides: [ 22 | { 23 | files: ['*.svelte'], 24 | parser: 'svelte-eslint-parser', 25 | parserOptions: { 26 | parser: '@typescript-eslint/parser' 27 | } 28 | } 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "supasvelte-962d8" 4 | } 5 | } -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. 🌟 Purpose 4 | 5 | A primary goal of SupaSvelteKit is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | ## 2. 🌍 Open [Source/Culture/Tech] Citizenship 10 | 11 | A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 12 | 13 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 14 | 15 | ## 3. 🌈 Expected Behavior 16 | 17 | The following behaviors are expected and requested of all community members: 18 | 19 | - Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 20 | - Exercise consideration and respect in your speech and actions. 21 | - Attempt collaboration before conflict. 22 | - Refrain from demeaning, discriminatory, or harassing behavior and speech. 23 | - Be mindful of your surroundings and of your fellow participants. 24 | 25 | ## 4. 🚫 Unacceptable Behavior 26 | 27 | The following behaviors are considered harassment and are unacceptable within our community: 28 | 29 | - Violence, threats of violence, or violent language directed against another person. 30 | - Sexist, racist, homophobic, transphobic, ableist, or otherwise discriminatory jokes and language. 31 | - Posting or displaying sexually explicit or violent material. 32 | - Posting or threatening to post other people’s personally identifying information ("doxing"). 33 | - Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 34 | - Inappropriate photography or recording. 35 | - Inappropriate physical contact. You should have someone’s consent before touching them. 36 | - Unwelcome sexual attention. 37 | 38 | ## 5. 🚷 Consequences of Unacceptable Behavior 39 | 40 | Unacceptable behavior from any community member will not be tolerated. Anyone asked to stop unacceptable behavior is expected to comply immediately. 41 | 42 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning. 43 | 44 | ## 6. 📢 Reporting Guidelines 45 | 46 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. 47 | 48 | ## 7. ⚖️ Addressing Grievances 49 | 50 | Only permanent resolutions (such as bans) may be appealed. To appeal a decision, contact [OpenFrenchFries](https://github.com/OpenFrenchFries) at hello@openfrenchfries.com with a concise description of your grievance. 51 | 52 | ## 8. 🌐 Scope 53 | 54 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues, online and in-person, as well as in all one-on-one communications pertaining to community business. 55 | 56 | --- 57 | 58 | This Code of Conduct is adapted from the [Citizen Code of Conduct](http://citizencodeofconduct.org/). -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SupaSvelteKit ⚡ 2 | 3 | First off, thank you for considering contributing to SupaSvelteKit! 🎉 Every contribution, whether it's a small typo fix or a new feature, is invaluable to the project's growth. This document provides guidelines and steps for contributing. 4 | 5 | ## 🤝 Code of Conduct 6 | 7 | By participating in this project, you're expected to uphold our [Code of Conduct](./CODE_OF_CONDUCT.md). Please ensure you read and understand its contents. 8 | 9 | ## 🛠 How Can I Contribute? 10 | 11 | ### 🐛 Reporting Bugs 12 | 13 | - Ensure the bug hasn't been reported already by searching the [issues](https://github.com/OpenFrenchFries/SupaSvelteKit/issues). 14 | - If you can't find an existing issue, [open a new one](https://github.com/OpenFrenchFries/SupaSvelteKit/issues/new). Be sure to include a clear title, a detailed description, and as much relevant information as possible. 15 | 16 | ### 💡 Suggesting Enhancements 17 | 18 | - First, read the documentation to ensure the enhancement isn't already a feature. 19 | - [Open a new issue](https://github.com/OpenFrenchFries/SupaSvelteKit/issues/new) and label it as an enhancement. 20 | - Provide a clear and detailed explanation of the enhancement and its benefits. 21 | 22 | ### 📥 Pull Requests 23 | 24 | 1. Fork the repository. 25 | 2. Create a new branch: `git checkout -b feature/your-feature-name`. 26 | 3. Make your changes and commit them: `git commit -m 'Add some feature'`. 27 | 4. Push the branch: `git push origin feature/your-feature-name`. 28 | 5. Submit a pull request. 29 | 30 | ## 🖥 Development Setup 31 | 32 | 1. Clone the repository: `git clone https://github.com/your-username/SupaSvelteKit.git`. 33 | 2. Navigate to the project directory: `cd SupaSvelteKit`. 34 | 3. Install dependencies: `npm install`. 35 | 4. Start the development server: `npm run dev`. 36 | 37 | ## ✨ Style Guidelines 38 | 39 | - Ensure your code follows the established coding style of the project. 40 | - Comment your code where necessary. 41 | - Write tests for new features or bug fixes. 42 | 43 | ## 💖 Recognition 44 | 45 | Contributors will be recognized in the project's README and on the project's website (if applicable). 46 | 47 | --- 48 | 49 | Thank you for making SupaSvelteKit even better! 🌟 Your efforts and contributions are deeply appreciated. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | **Describe the Bug 🐛** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected Behavior 💡** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots 📸** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Environment:** 24 | - OS: [e.g. macOS, Windows, Linux] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | - SupaSvelteKit version: [e.g. v1.0.0] 28 | 29 | **Additional Context 📋** 30 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: enhancement 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe. 🐛** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like 💡** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered 🔄** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context 📋** 17 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 🛠 Description of Changes 2 | 3 | Please provide a brief summary of the changes made in this pull request. 4 | 5 | ## 🚩 Linked Issue(s) 6 | 7 | - Resolves # [issue number] 8 | - Closes # [issue number] 9 | - Related to # [issue number] 10 | 11 | ## 🧪 How to Test 12 | 13 | 1. Steps to reproduce the changes or verify the fixes. 14 | 2. Any specific areas to focus on during testing. 15 | 16 | ## 📸 Screenshots (if applicable) 17 | 18 | Attach any relevant screenshots or screen recordings. 19 | 20 | ## 📋 Additional Notes 21 | 22 | Any additional information or context you'd like to provide. 23 | 24 | ## ✅ Checklist 25 | 26 | - [ ] I have read and understand the [contributing guidelines](./CONTRIBUTING.md). 27 | - [ ] I have checked that no existing PRs fulfill the same purpose. 28 | - [ ] I have tested my changes. 29 | - [ ] I have updated the documentation accordingly (if applicable). -------------------------------------------------------------------------------- /.github/workflows/firebase-deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Firebase Hosting 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | defaults: 9 | run: 10 | working-directory: ./docs 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: "20.x" 20 | 21 | - name: Cache node modules 22 | id: cache-npm 23 | uses: actions/cache@v3 24 | env: 25 | cache-name: cache-node-modules 26 | with: 27 | # npm cache files are stored in `~/.npm` on Linux/macOS 28 | path: ~/.npm 29 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 30 | restore-keys: | 31 | ${{ runner.os }}-build-${{ env.cache-name }}- 32 | ${{ runner.os }}-build- 33 | ${{ runner.os }}- 34 | 35 | - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} 36 | name: List the state of node modules 37 | continue-on-error: true 38 | run: npm list 39 | 40 | - run: npm i 41 | - run: npm run build 42 | 43 | - id: 'auth' 44 | name: 'Authenticate to Google Cloud' 45 | uses: 'google-github-actions/auth@v1' 46 | with: 47 | credentials_json: ${{ secrets.GCP_SA_KEY }} 48 | 49 | - run: npm run deploy -------------------------------------------------------------------------------- /.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: "20.x" 15 | registry-url: "https://registry.npmjs.org" 16 | 17 | - name: Cache node modules 18 | id: cache-npm 19 | uses: actions/cache@v3 20 | env: 21 | cache-name: cache-node-modules 22 | with: 23 | # npm cache files are stored in `~/.npm` on Linux/macOS 24 | path: ~/.npm 25 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 26 | restore-keys: | 27 | ${{ runner.os }}-build-${{ env.cache-name }}- 28 | ${{ runner.os }}-build- 29 | ${{ runner.os }}- 30 | 31 | - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} 32 | name: List the state of node modules 33 | continue-on-error: true 34 | run: npm list 35 | 36 | - run: npm i 37 | - run: npm run package 38 | - name: Publish package to NPM 39 | run: npm publish 40 | env: 41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: 'run-tests' 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: pnpm/action-setup@v2 11 | with: 12 | version: 8 13 | - uses: supabase/setup-cli@v1 14 | with: 15 | version: latest 16 | - run: supabase start 17 | - run: supabase db reset 18 | - run: supabase functions serve --no-verify-jwt & 19 | - run: pnpm i 20 | - run: pnpm run env:local 21 | - run: npx playwright install --with-deps chromium 22 | - run: npx playwright test --workers 5 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /dist 5 | /.svelte-kit 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | vite.config.js.timestamp-* 11 | vite.config.ts.timestamp-* 12 | 13 | # Ignore generated credentials from google-github-actions/auth 14 | gha-creds-*.json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OpenFrenchFries 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SupaSvelteKit ⚡ 2 | 3 | > 🌟 Where Svelte's elegance meets Supabase's might! 🌟 4 | 5 | SupaSvelteKit is a library designed to seamlessly integrate SvelteKit with Supabase, providing developers with a powerful toolkit to build dynamic web applications. 6 | 7 | ## 🎉 Features 8 | 9 | - **Authentication** 🔐: Easily integrate Supabase authentication with SvelteKit. 10 | - **Realtime** ⏱️: Simplified multi-user realtime updates with Svelte stores. 11 | - **Storage** 📦: Manage your Supabase storage with Svelte components. 12 | - ... and more to come! Stay tuned and feel free to suggest or contribute new features! 13 | 14 | ## 🛠 Installation 15 | 16 | ```bash 17 | npm install supasveltekit 18 | ``` 19 | 20 | ## 🚀 Quick Start 21 | 22 | 1. Initialize your Supabase client: 23 | ```src/lib/supabase.ts``` 24 | ```ts 25 | import { PUBLIC_SUPABASE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'; 26 | import { createClient } from '@supabase/supabase-js'; 27 | 28 | export const supabaseUrl = PUBLIC_SUPABASE_URL; 29 | export const supabaseKey = PUBLIC_SUPABASE_KEY; 30 | 31 | export const supabase = createClient(supabaseUrl, supabaseKey); 32 | ``` 33 | 34 | 2. Use SupaSvelteKit components in your SvelteKit app: 35 | ```src/routes/+page.svelte``` 36 | ```svelte 37 | 41 | 42 | 43 | 44 |

Welcome, {session?.user?.identities?.[0]?.identity_data?.email}!

45 | 46 | 47 | 48 | {#if error !== null} 49 |
{error}
50 | {/if} 51 |
    52 | {#each bucketFiles as file} 53 |
  • 54 | {file.name} 55 |
  • 56 | {/each} 57 |
58 |
59 | 60 | 61 |
62 | {#if error} 63 |

Error: {error.message}

64 | {:else if !state} 65 |

Loading...

66 | {:else} 67 |

Connected to channel {channel?.name}

68 |

Number of users: {state?.count}

69 | 70 | 71 | {/if} 72 |
73 |
74 |
75 | 76 | 77 |

Sign in to continue

78 | 79 |
80 |
81 | ``` 82 | 83 | ## 📚 Documentation 84 | 85 | For detailed documentation, usage guides, and API references, dive into [our documentation site](http://SupaSvelteKit.openfrenchfries.com/). 86 | 87 | ## 📖 Examples 88 | 89 | Explore hands-on examples to get a feel for how SupaSvelteKit enhances your projects: 90 | 91 | - [Basic Todo App](https://github.com/OpenFrenchFries/supasveltekit-example-todo) 92 | - [Authentication Demo](https://github.com/orgs/OpenFrenchFries/repositories) 93 | - ... and more examples coming soon! If you've built something cool with SupaSvelteKit, let us know! 94 | 95 | ## 💪 Contributing 96 | 97 | Join the SupaSvelteKit journey! 🌍 Whether you're fixing bugs, suggesting enhancements, or enriching the documentation, every contribution counts. Dive into our [CONTRIBUTING.md](.github/CONTRIBUTING.md) to get started. 98 | 99 | ## 📜 License 100 | 101 | Freedom with responsibility! SupaSvelteKit is [MIT licensed](LICENSE), ensuring open use with acknowledgment. 102 | 103 | ## 🙌 Acknowledgements 104 | 105 | A big shoutout 📣: 106 | 107 | - To the Svelte and Supabase communities for lighting the way with invaluable resources and support. 108 | - To every coder, contributor, and coffee mug that's been part of this journey. ☕ 109 | 110 | --- 111 | 112 | Crafted with 🧡 by [OpenFrenchFries](https://github.com/OpenFrenchFries) and the amazing contributors. [Join us!](.github/CONTRIBUTING.md) 113 | -------------------------------------------------------------------------------- /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 | .vscode/ 23 | 24 | # Ignore generated credentials from google-github-actions/auth 25 | gha-creds-*.json -------------------------------------------------------------------------------- /docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import starlight from '@astrojs/starlight'; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | integrations: [ 7 | starlight({ 8 | title: 'SupaSvelteKit', 9 | social: { 10 | github: 'https://github.com/OpenFrenchFries/SupaSvelteKit', 11 | }, 12 | sidebar: [ 13 | { 14 | label: 'Getting Started', 15 | autogenerate: { directory: 'guides' }, 16 | }, 17 | { 18 | label: 'Components', 19 | autogenerate: { directory: 'reference' }, 20 | }, 21 | ], 22 | }), 23 | ], 24 | 25 | // Process images with sharp: https://docs.astro.build/en/guides/assets/#using-sharp 26 | image: { service: { entrypoint: 'astro/assets/services/sharp' } }, 27 | }); 28 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supabase-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 --only hosting" 12 | }, 13 | "dependencies": { 14 | "@astrojs/starlight": "^0.15.0", 15 | "astro": "^3.6.5", 16 | "sharp": "^0.33.0" 17 | }, 18 | "devDependencies": { 19 | "firebase-tools": "^12.5.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/src/assets/houston.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFrenchFries/supasveltekit/3403a9a34c72d0697aa92997a8b9c69e80d8189e/docs/src/assets/houston.webp -------------------------------------------------------------------------------- /docs/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from 'astro:content'; 2 | import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'; 3 | 4 | export const collections = { 5 | docs: defineCollection({ schema: docsSchema() }), 6 | i18n: defineCollection({ type: 'data', schema: i18nSchema() }), 7 | }; 8 | -------------------------------------------------------------------------------- /docs/src/content/docs/guides/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | description: A guide page to a quick first use of SupaSvelteKit. 4 | --- 5 | 6 | ## 🚀 Quick Start 7 | 8 | 1. Install SupaSvelteKit: 9 | 10 | ```bash 11 | npm install supasveltekit 12 | ``` 13 | 14 | 2. Initialize your Supabase client: 15 | 16 | ```ts 17 | import { PUBLIC_SUPABASE_KEY, PUBLIC_SUPABASE_URL } from "$env/static/public"; 18 | import { createClient } from "@supabase/supabase-js"; 19 | 20 | export const supabaseUrl = PUBLIC_SUPABASE_URL; 21 | export const supabaseKey = PUBLIC_SUPABASE_KEY; 22 | 23 | export const supabase = createClient(supabaseUrl, supabaseKey); 24 | ``` 25 | 26 | 3. Use SupaSvelteKit components in your SvelteKit app: 27 | 28 | ```svelte 29 | 33 | 34 | 35 |

Welcome, {session?.user?.identities?.[0]?.identity_data?.email}!

36 | 37 | 38 | 39 | {#if error !== null} 40 |
{error}
41 | {/if} 42 |
    43 | {#each bucketFiles as file} 44 |
  • 45 | {file.name} 46 |
  • 47 | {/each} 48 |
49 |
50 | 51 | 52 |
53 | {#if error} 54 |

Error: {error.message}

55 | {:else if !state} 56 |

Loading...

57 | {:else} 58 |

Connected to channel {channel?.name}

59 |

Number of users: {state?.count}

60 | 61 | 62 | {/if} 63 |
64 |
65 |
66 | 67 | 68 |

Sign in to continue

69 | 70 |
71 |
72 | ``` 73 | -------------------------------------------------------------------------------- /docs/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | description: A guide page to a quick first use of SupaSvelteKit. 4 | --- 5 | 6 | ## 🚀 Quick Start 7 | 8 | 1. Install SupaSvelteKit: 9 | 10 | ```bash 11 | npm install supasveltekit 12 | ``` 13 | 14 | 2. Initialize your Supabase client: 15 | 16 | ```ts 17 | import { PUBLIC_SUPABASE_KEY, PUBLIC_SUPABASE_URL } from "$env/static/public"; 18 | import { createClient } from "@supabase/supabase-js"; 19 | 20 | export const supabaseUrl = PUBLIC_SUPABASE_URL; 21 | export const supabaseKey = PUBLIC_SUPABASE_KEY; 22 | 23 | export const supabase = createClient(supabaseUrl, supabaseKey); 24 | ``` 25 | 26 | 3. Use SupaSvelteKit components in your SvelteKit app: 27 | 28 | ```svelte 29 | 33 | 34 | 35 |

Welcome, {session?.user?.identities?.[0]?.identity_data?.email}!

36 | 37 | 38 | 39 | {#if error !== null} 40 |
{error}
41 | {/if} 42 |
    43 | {#each bucketFiles as file} 44 |
  • 45 | {file.name} 46 |
  • 47 | {/each} 48 |
49 |
50 | 51 | 52 |
53 | {#if error} 54 |

Error: {error.message}

55 | {:else if !state} 56 |

Loading...

57 | {:else} 58 |

Connected to channel {channel?.name}

59 |

Number of users: {state?.count}

60 | 61 | 62 | {/if} 63 |
64 |
65 |
66 | 67 | 68 |

Sign in to continue

69 | 70 |
71 |
72 | ``` 73 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/App/supabase-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Supabase App 3 | description: How to use the Supabase App component. 4 | --- 5 | 6 | The `SupabaseApp` component is used to wrap your app and provide the Supabase client to the rest of your app. 7 | 8 | ## Props 9 | 10 | | Prop name | Type | Description | 11 | | ---------- | ---------------- | --------------------------- | 12 | | `supabase` | `SupabaseClient` | The Supabase client object. | 13 | 14 | ## Slots 15 | 16 | This component has no slots. 17 | 18 | ## Usage 19 | 20 | ```svelte 21 | 25 | 26 | 27 | 28 | 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Authentication/authenticated.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Authenticated 3 | description: How to use the Authenticated component. 4 | --- 5 | 6 | The `Authenticated` component is used to render content only when a user is authenticated. It also provides a `signOut` function that can be used to sign the user out. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ### Props 11 | 12 | This component does not accept any props. 13 | 14 | ### Slots 15 | 16 | | Property | Description | 17 | | --------- | ----------------------------------- | 18 | | `session` | The user session data. | 19 | | `auth` | The Supabase authentication client. | 20 | | `error` | The authentication error, if any. | 21 | | `signOut` | A function that signs out the user. | 22 | 23 | ## Usage 24 | 25 | ```svelte 26 | 29 | 30 | 31 |

Only authenticated users can see this

32 | 33 |
34 | ``` 35 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Authentication/session-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Session Store 3 | description: How to use the Session Store. 4 | --- 5 | 6 | The `SessionStore` component is used to store the session data in a Svelte store, which can then be used throughout your app. This is useful for accessing the session data in components that are not children of the `SupabaseApp` component. 7 | 8 | The session store is a readable store that contains the session data. It can be used to access the session data and subscribe to changes in the session data. 9 | 10 | ## Usage 11 | 12 | ```svelte 13 | 22 | 23 | {#if $session.data !== null} 24 |

Welcome, {$session.data.user?.identities?.[0]?.identity_data?.email}!

25 | 26 | {:else} 27 |

Sign in to continue

28 | 29 | {/if} 30 | ``` -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Authentication/unauthenticated.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Unauthenticated 3 | description: How to use the Unauthenticated component. 4 | --- 5 | 6 | The `Unauthenticated` component is used to render content only when a user is not authenticated. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ### Props 11 | 12 | This component does not accept any props. 13 | 14 | ### Slots 15 | 16 | | Property | Description | 17 | | --------- | ----------------------------------- | 18 | | `auth` | The Supabase authentication client. | 19 | | `error` | The authentication error, if any. | 20 | 21 | ## Usage 22 | 23 | ```svelte 24 | 27 | 28 | 29 |

Only unauthenticated users can see this!

30 |
31 | ``` -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Database/collection.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collection 3 | description: How to use the Collection component. 4 | --- 5 | 6 | The `Collection` component is used to fetch data from a Supabase database table. It uses a svelte store to store and forward the data received from the table to wrapped components. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. It offers a real-time mode that listens for changes to the table and updates the store accordingly. 9 | 10 | ### Props 11 | 12 | This component is responsible for fetching and displaying data from a Supabase database table. It uses the `selectStore` function from the `database.js` store to fetch the data and store it in a Svelte store. The component accepts the following props: 13 | 14 | | Prop Name | Type | Description | 15 | | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 16 | | `realtime` | Boolean | Determines whether the component should listen for real-time changes to the table. When set to `true`, the component sets up subscriptions to listen for changes to the specified table in the Supabase database. When a row is deleted, it is removed from the store. When a row is inserted, it is added to the store. When a row is updated, it is upgraded in the store. | 17 | | `refreshKey` | String | Specifies the key to use when refreshing the data in the store. | 18 | | `table` | String | Specifies the name of the table to fetch data from. | 19 | | `selectQuery` | String | Specifies the SQL query to use when fetching data from the table. Defaults to `*`. | 20 | | `head` | Boolean | Determines whether to include the column names in the data. Defaults to `false`. | 21 | | `schema` | String | Specifies the schema to use when fetching data from the table. Defaults to `public`. | 22 | | `count` | String | Specifies the type of count to use when fetching data from the table. Can be one of `'exact'`, `'planned'`, `'estimated'`, or `undefined`. Defaults to `undefined`. | 23 | 24 | ### Slots 25 | 26 | The component has two slots: 27 | 28 | - `default`: This slot is rendered when the data has been fetched successfully. It receives the following props: 29 | - `payload`: The data fetched from the table. 30 | - `error`: An error object if an error occurred while fetching the data, otherwise `null`. 31 | - `loading`: This slot is rendered while the data is being fetched. 32 | 33 | ## Usage 34 | 35 | ```svelte 36 | 40 | 41 | 42 | {#if !payload} 43 |

Loading...

44 | {:else if error} 45 |

{error.message}

46 | {:else} 47 |

Count: {payload.length}

48 | {#each payload as entry} 49 |

ID: {entry.id} Created at {entry.created_at}

50 | {/each} 51 | {/if} 52 |
53 |

Loading...

54 |
55 |
56 | ``` 57 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Database/item.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Item 3 | description: How to use the Item component. 4 | --- 5 | 6 | The `Item` component is used to display a single item from a Supabase database table. It uses the `itemStore` store to fetch the item and the `getSupabaseContext` function to get the Supabase client. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ### Props 11 | 12 | This component is used to display a single item from a Supabase database table. It takes in the following props: 13 | 14 | | Prop | Description | 15 | |----------|-----------------------------------------------------------------------------------------------| 16 | | realtime | A boolean that determines whether or not to listen for real-time changes to the specified table | 17 | | refKey | The name of the column that serves as the reference key for the item | 18 | | refValue | The value of the reference key for the item | 19 | | table | The name of the table to retrieve the item from | 20 | | schema | The name of the schema that the table belongs to (defaults to 'public') | 21 | 22 | If realtime is enabled, the component sets up subscriptions to listen for changes to the specified table in the Supabase database. When a row is deleted, it is removed from the store. When a row is inserted, it is added to the store. When a row is updated, it is upgraded in the store. 23 | 24 | ### Slots 25 | 26 | The component has two slots: 27 | - default: this slot is used to display the item data. It receives the following props: 28 | - payload: the data for the item 29 | - error: any error that occurred while retrieving the item 30 | - loading: this slot is used to display a loading indicator while the item is being retrieved from the database. 31 | 32 | ## Usage 33 | 34 | ```svelte 35 | 41 | 42 | 43 |

{JSON.stringify(payload)}

44 |
45 | ``` 46 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Functions/delayed-function-call.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Delayed function call 3 | description: How to use the Delayed Function Call component. 4 | --- 5 | 6 | A Svelte component that allows for delayed function calls using Supabase Functions. 7 | 8 | ## Props 9 | 10 | | Parameter | Type | Description | 11 | | -------------- | ------ | ------------------------------------------------- | 12 | | `functionName` | string | The name of the Supabase Function to call. | 13 | | `headers` | any | Optional headers to include in the function call. | 14 | | `body` | any | Optional body to include in the function call. | 15 | | `delay` | number | The delay in milliseconds before making the call. | 16 | 17 | ## Slots 18 | 19 | - `default`: The default slot that receives the payload, error, and functions client. 20 | 21 | | Parameter | Type | Description | 22 | | ----------- | ----------------- | ------------------------------------------------- | 23 | | `payload` | `any \| null` | The payload returned by the function call. | 24 | | `error` | `Error \| null` | Any error that occurred during the function call. | 25 | | `functions` | `FunctionsClient` | The Supabase Functions client. | 26 | 27 | - `loading`: The slot to show while the function call is being delayed. 28 | 29 | ## Usage 30 | 31 | ```svelte 32 | 36 | 37 | 38 |
39 | {#if payload} 40 |

The time is {payload.time}

41 | {:else if error} 42 |

There was an error: {JSON.stringify(error)}

43 | {:else} 44 |

Loading...

45 | {/if} 46 |
47 |
48 | ``` 49 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Functions/function-call.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Function call 3 | description: How to use the Function Call component. 4 | --- 5 | 6 | This component represents a function call to a Supabase function. 7 | 8 | ## Props 9 | 10 | | Prop | Type | Description | 11 | | -------------- | ------ | ------------------------------------------------- | 12 | | `functionName` | string | The name of the Supabase function to call. | 13 | | `headers` | any | Optional headers to include in the function call. | 14 | | `body` | any | Optional body to include in the function call. | 15 | 16 | ## Slots 17 | 18 | - `default`: This slot is rendered when the function call is successful. It has the following scope variables: 19 | 20 | | Variable | Type | Description | 21 | | ----------- | ----------------- | ------------------------------------------------- | 22 | | `payload` | `any\| null` | The response payload from the function call. | 23 | | `error` | `Error\| null` | Any error that occurred during the function call. | 24 | | `functions` | `FunctionsClient` | The Supabase functions client. | 25 | 26 | - `loading`: This slot is rendered while the function call is in progress. 27 | 28 | ## Usage 29 | 30 | ```svelte 31 | 34 | 35 | 36 |
37 | {#if payload} 38 |

The time is {payload.time}

39 | {:else if error} 40 |

There was an error: {JSON.stringify(error)}

41 | {:else} 42 |

Loading...

43 | {/if} 44 |
45 |
46 | ``` 47 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Functions/interval-function-call.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Interval function call 3 | description: How to use the Interval Function Call component. 4 | --- 5 | 6 | This component represents a Supabase function call that is executed at regular intervals. 7 | 8 | ## Props 9 | 10 | | Prop | Description | 11 | | -------------- | ----------------------------------------------------------------------------- | 12 | | `functionName` | The name of the function to call. | 13 | | `headers` | Optional headers to include in the function call. | 14 | | `body` | Optional body to include in the function call. | 15 | | `interval` | The interval (in milliseconds) at which the function call should be executed. | 16 | 17 | ## Slots 18 | 19 | - `default`: This slot is rendered when the `store` is truthy. It receives the following context variables: 20 | 21 | | Variable | Description | 22 | | ----------- | ------------------------------------------------- | 23 | | `payload` | The data returned by the function call. | 24 | | `error` | Any error that occurred during the function call. | 25 | | `functions` | The `FunctionsClient` instance. | 26 | 27 | - `loading`: This slot is rendered when the `store` is falsy. 28 | 29 | ## Usage 30 | 31 | ```svelte 32 | 36 | 37 | 38 |
39 | {#if payload} 40 |

The time is {payload.time}

41 | {:else if error} 42 |

There was an error: {JSON.stringify(error)}

43 | {:else} 44 |

Loading...

45 | {/if} 46 |
47 |
48 | ``` 49 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Functions/scheduled-function-call.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Scheduled function call 3 | description: How to use the Scheduled Function Call component. 4 | --- 5 | 6 | This component represents a scheduled supabase function call. It allows users to specify the function name, headers, body, and date for the function call. 7 | 8 | ## Props 9 | 10 | | Prop | Description | 11 | | ------------ | ----------------------------------------------------------------- | 12 | | functionName | The name of the function to be called. | 13 | | headers | Optional headers to be included in the function call. | 14 | | body | Optional body data to be sent with the function call. | 15 | | date | The date and time at which the function call should be scheduled. | 16 | 17 | ## Slots 18 | 19 | | Slot | Description | 20 | | ------- | ---------------------------------------------------------------------------- | 21 | | default | The default slot receives the payload, error, and functions client as props. | 22 | | loading | The loading slot is rendered when the function call is being processed. | 23 | 24 | ## Usage 25 | 26 | ```svelte 27 | 31 | 32 | 33 |
34 | {#if payload} 35 |

The time is {payload.time}

36 | {:else if error} 37 |

There was an error: {JSON.stringify(error)}

38 | {:else} 39 |

Loading...

40 | {/if} 41 |
42 |
43 | ``` 44 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Realtime/broadcast-channel.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Broadcast Channel 3 | description: How to use the Broadcast Channel component. 4 | --- 5 | 6 | The `BroadcastChannel` component is used to listen to a channel on the Supabase Realtime service. It uses a svelte store to store and forward the data received from the channel to wrapped components. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ### Props 11 | 12 | | Prop name | Type | Default value | Description | 13 | | ------------- | -------- | ------------- | ------------------------------------- | 14 | | `channelName` | `string` | `'any'` | The name of the channel to listen to. | 15 | | `eventName` | `string` | `'sync'` | The name of the event to listen to. | 16 | 17 | ### Slots 18 | 19 | | Property | Type | Description | 20 | | ---------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | 21 | | `payload` | `Record \| null` | The data payload received from the channel. | 22 | | `error` | `Error \| null` | The error object if there was an error receiving the payload. | 23 | | `realtime` | `RealtimeClient` | The Supabase RealtimeClient instance. | 24 | | `channel` | `RealtimeChannel \| null` | The RealtimeChannel instance for the channel being listened to. This will be `null` if the channel has not been successfully subscribed to yet. | 25 | 26 | ## Usage 27 | 28 | ```svelte 29 | 32 | 33 | 34 | {#if error !== null} 35 |
{error.message}
36 | {/if} 37 | {#if payload !== null} 38 |
{JSON.stringify(payload)}
39 | {/if} 40 |
41 | ``` 42 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Realtime/db-changes-channel.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DB Changes 3 | description: How to use the DB Changes component. 4 | --- 5 | 6 | The `DBChanges` component is used to listen to changes on a table in the Supabase Realtime service. It uses a svelte store to store and forward the data received from the channel to wrapped components. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ## Props 11 | 12 | | Prop name | Description | Default value | 13 | | ------------- | --------------------------------------------------------------------------------------------------------- | ------------- | 14 | | `channelName` | The name of the channel to listen to changes on. | `'any'` | 15 | | `event` | The type of event to listen to changes for. Can be one of `"*"`, `"INSERT"`, `"UPDATE"`, or `"DELETE"`. | `"*"` | 16 | | `schema` | The name of the schema to listen to changes on. | `"*"` | 17 | | `table` | The name of the table to listen to changes on. If `null`, listens to changes on all tables in the schema. | `null` | 18 | | `filter` | A filter expression to apply to the changes. If `null`, no filter is applied. | `null` | 19 | 20 | ## Slots 21 | 22 | - `default`: The default slot is rendered with the latest changes. It has access to the following props: 23 | 24 | | Property | Description | 25 | | ---------- | ----------------------------------------------------------- | 26 | | `payload` | The latest changes to the database table. | 27 | | `error` | An error object if there was an error listening to changes. | 28 | | `realtime` | The Supabase RealtimeClient instance. | 29 | | `channel` | The Supabase RealtimeChannel instance. | 30 | 31 | - `loading`: The loading slot is rendered while the component is waiting for the initial changes to load. 32 | 33 | ## Usage 34 | 35 | ```svelte 36 | 39 | 40 | 41 | {#if error !== null} 42 |
{error.message}
43 | {/if} 44 | {#if payload !== null} 45 |

Last change received: >{payload?.eventType ?? "none"}

46 | {/if} 47 |
48 | ``` 49 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Realtime/realtime-presence.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Realtime Presence 3 | description: How to use the Realtime Presence component. 4 | --- 5 | 6 | The `RealtimePresence` component is used to listen to a channel on the Supabase Realtime service to track users' presence. It uses a svelte store to store and forward the data received from the channel to wrapped components. It also provides two dispatch functions to receive join and leave events. This component is responsible for managing the presence of users in a Supabase Realtime channel. It leverages the `presenceStateStore` and `userStatusStore`, which are used to manage the state of the presence channel and the user's status, respectively. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ### Props: 11 | 12 | | Prop Name | Type | Description | 13 | | ---------------- | ------------------------------------- | --------------------------------------------------------------------------- | 14 | | `channelName` | `string` | The name of the channel to connect to. Defaults to `'any'`. | 15 | | `userStatus` | `Record \| undefined` | The default user's status. Defaults to `undefined`. | 16 | | `channelOptions` | `RealtimeChannelOptions \| undefined` | The options to use when connecting to the channel. Defaults to `undefined`. | 17 | 18 | ### Slots: 19 | 20 | | Prop Name | Type | Description | 21 | | -------------- | --------------------------------------- | ----------------------------------------------------------------- | 22 | | `state` | `RealtimePresenceState \| null` | The state containing presences data. | 23 | | `realtime` | `RealtimeClient` | The Supabase Realtime client. | 24 | | `error` | `Error \| null` | The error, if any, that occurred while connecting to the channel. | 25 | | `channel` | `RealtimeChannel \| null` | The Supabase Realtime channel. | 26 | | `updateStatus` | `(status: Record) => void` | A function that can be used to update the user's status. | 27 | 28 | ## Usage 29 | 30 | ```svelte 31 | 32 |
33 | {#if error} 34 |

Error: {error.message}

35 | {:else if !state} 36 |

Loading...

37 | {:else} 38 |

Connected to channel {channel?.name}

39 |

Number of users: {state?.count}

40 | 41 | 42 | {/if} 43 |
44 |
45 | ``` 46 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Storage/bucket-context.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bucket Context 3 | description: How to use the Bucket Context. 4 | --- 5 | 6 | The `BucketContext` component is used to provide a bucket to `BucketFilesList`, `DownloadURL` and `Uploader`. It can be used to provide a bucket name and path to any other component that needs it. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ### Props 11 | 12 | | Prop name | Type | Description | 13 | | ------------ | ------ | ------------------------------------------------------------- | 14 | | `bucketName` | string | The name of the bucket to set the context for. | 15 | | `path` | string | The path to set the context for. Defaults to an empty string. | 16 | 17 | ### Slots 18 | 19 | This component has no slots. 20 | 21 | ## Usage 22 | 23 | ```svelte 24 | 27 | 28 | 29 | 30 | {#if error !== null} 31 |
{error}
32 | {/if} 33 |
    34 | {#each bucketFiles as file} 35 |
  • 36 | {file.name} 37 |
  • 38 | {/each} 39 |
40 |
41 |
42 | ``` 43 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Storage/bucket-files-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bucket Files List 3 | description: How to use the Bucket Files List component. 4 | --- 5 | 6 | The `BucketFilesList` component is used to list files in a bucket. This component must be a child of the `SupabaseApp` component to work. 7 | 8 | To use this component, you must provide a bucket name and path. The bucket name is the name of the bucket you want to list files from. The path is the path in the bucket you want to list files from. If you want to list files from the root of the bucket, you can leave the path empty. 9 | 10 | You should have the `select` permission on `storage.objects` or on your bucket to be able to list files in it. 11 | 12 | ## Props 13 | 14 | | Prop name | Type | Description | 15 | | ------------ | ------ | -------------------------------------------------------------------------------------------------------------- | 16 | | `bucketName` | string | The name of the bucket to retrieve files from. Defaults to the bucket name from the `BucketContext`. | 17 | | `path` | string | The path of the directory in the bucket to retrieve files from. Defaults to the path from the `BucketContext`. | 18 | 19 | ## Slots 20 | 21 | | Prop name | Type | Description | 22 | | ------------- | -------- | --------------------------------------------------------------------------- | 23 | | `bucketFiles` | `Array` | An array of file objects retrieved from the specified bucket and path. | 24 | | `storage` | `Object` | The storage client object from the Supabase SDK. | 25 | | `error` | `Error` | An error object if there was an error retrieving the files, otherwise null. | 26 | 27 | ## Usage 28 | 29 | ```svelte 30 | 33 | 34 | 35 | {#if error !== null} 36 |
{error}
37 | {/if} 38 |
    39 | {#each bucketFiles as file} 40 |
  • 41 | {file.name} 42 |
  • 43 | {/each} 44 |
45 |
46 | ``` 47 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Storage/buckets-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Buckets List 3 | description: How to use the Buckets List component. 4 | --- 5 | 6 | The `BucketsList` component displays a list of buckets retrieved from Supabase Storage. It uses the `bucketsListStore` store to fetch the list of buckets and the `getSupabaseContext` function to get the Supabase Storage client. This component must be a child of the `SupabaseApp` component to work. 7 | 8 | You should have the `select` permission on `storage.buckets` to be able to list buckets. 9 | 10 | ### Props 11 | 12 | This component has no props. 13 | 14 | ### Slots 15 | 16 | This component has one slot named `default` which accepts the following props: 17 | 18 | | Name | Description | 19 | | --------- | ------------------------------------------------------------------------------------------ | 20 | | `buckets` | An array of `Bucket` objects representing the list of buckets. | 21 | | `error` | An `Error` object representing any error that occurred while fetching the list of buckets. | 22 | | `storage` | A `StorageClient` object representing the Supabase Storage client. | 23 | 24 | ## Usage 25 | 26 | ```svelte 27 | 30 | 31 | 32 | {#if error !== null} 33 |
{error}
34 | {/if} 35 |
    36 | {#each buckets as bucket} 37 |
  • 38 | {bucket.name} 39 |
  • 40 | {/each} 41 |
42 |
43 | ``` 44 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Storage/download-url.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Download URL 3 | description: How to use the Download URL component. 4 | --- 5 | 6 | The `DownloadURL` component retrieves a download URL for a file in a Supabase Storage bucket. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ## Props 11 | 12 | | Prop | Type | Default | Required | Description | 13 | | ------------ | -------- | ------- | -------- | ----------------------------------------------------- | 14 | | `bucketName` | `string` | | Yes | The name of the bucket containing the file. | 15 | | `path` | `string` | | Yes | The path to the file in the bucket. | 16 | | `expiration` | `number` | `60` | No | The number of seconds until the download URL expires. | 17 | 18 | ## Slots 19 | 20 | | Name | Description | 21 | | --------- | -------------------------------------------------------------------------------- | 22 | | `default` | The default slot that receives the download URL, storage client, and any errors. | 23 | 24 | ## Usage 25 | 26 | ```svelte 27 | 30 | 31 | 32 | {#if url} 33 | Download file 34 | {:else if error} 35 |

Error: {error.message}

36 | {:else} 37 |

Loading...

38 | {/if} 39 |
40 | ``` 41 | -------------------------------------------------------------------------------- /docs/src/content/docs/reference/Storage/uploader.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Uploader 3 | description: How to use the Uploader component. 4 | --- 5 | 6 | The `Uploader` component is used to upload files to a bucket. 7 | 8 | This component must be a child of the `SupabaseApp` component to work. 9 | 10 | ### Props 11 | 12 | | Prop name | Type | Description | 13 | | --------- | ------ | -------------------------------------------------------------------------------- | 14 | | `file` | File | The file to upload. | 15 | | `options` | object | The options to pass to the `storage.upload` method. Defaults to an empty object. | 16 | 17 | ### Slots 18 | 19 | | Prop name | Type | Description | 20 | | -------------- | -------- | --------------------------------------------------------------------------- | 21 | | `storage` | `Object` | The storage client object from the Supabase SDK. | 22 | | `error` | `Error` | An error object if there was an error uploading the file, otherwise null. | 23 | | `uploadedFile` | `string` | The name of the uploaded file if the upload was successful, otherwise null. | 24 | 25 | ## Usage 26 | 27 | ```svelte 28 | 33 | 34 | 35 | (file = e?.currentTarget?.files?.[0] ?? null)} /> 36 | 37 | {#if file} 38 | 39 | {#if error} 40 |
{error.message}
41 | {/if} 42 | {#if uploadedFile} 43 |
{uploadedFile}
44 | {/if} 45 |
46 | {/if} 47 |
48 | ``` 49 | -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict" 3 | } -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "docs/dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ] 9 | } 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supasveltekit", 3 | "description": "🌟 Where Svelte's elegance meets Supabase's might! 🌟", 4 | "license": "MIT", 5 | "version": "0.1.6", 6 | "engines": { 7 | "node": ">=18.18.0" 8 | }, 9 | "scripts": { 10 | "dev": "vite dev", 11 | "build": "vite build && npm run package", 12 | "preview": "vite preview", 13 | "package": "svelte-kit sync && svelte-package && publint", 14 | "prepublishOnly": "npm run package", 15 | "test": "npm run test:integration && npm run test:unit", 16 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 17 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 18 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 19 | "format": "prettier --plugin-search-dir . --write .", 20 | "supabase": "supabase", 21 | "env:local": "echo \"$(supabase status -o env --override-name auth.anon_key=PUBLIC_SUPABASE_KEY --override-name api.url=PUBLIC_SUPABASE_URL)\nNODE_ENV=\\\"development\\\"\nPUBLIC_TEST_USER_EMAIL=\\\"test@test.io\\\"\nPUBLIC_TEST_USER_PASSWORD=\\\"test1234\\\"\" > .env", 22 | "test:integration": "playwright test", 23 | "test:unit": "vitest" 24 | }, 25 | "exports": { 26 | ".": { 27 | "types": "./dist/index.d.ts", 28 | "svelte": "./dist/index.js" 29 | } 30 | }, 31 | "files": [ 32 | "dist", 33 | "!dist/**/*.test.*", 34 | "!dist/**/*.spec.*" 35 | ], 36 | "peerDependencies": { 37 | "@supabase/supabase-js": "^2.33.1", 38 | "svelte": "^4.0.0" 39 | }, 40 | "devDependencies": { 41 | "@playwright/test": "^1.46.0", 42 | "@sveltejs/adapter-auto": "^3.2.2", 43 | "@sveltejs/kit": "^2.5.20", 44 | "@sveltejs/package": "^2.3.2", 45 | "@sveltejs/vite-plugin-svelte": "^3.1.1", 46 | "@typescript-eslint/eslint-plugin": "^8.0.1", 47 | "@typescript-eslint/parser": "^8.0.1", 48 | "dotenv": "^16.4.5", 49 | "eslint": "^9.8.0", 50 | "eslint-config-prettier": "^9.1.0", 51 | "eslint-plugin-svelte": "^2.43.0", 52 | "prettier": "^3.3.3", 53 | "prettier-plugin-svelte": "^3.2.6", 54 | "publint": "^0.2.9", 55 | "supabase": "^1.187.10", 56 | "svelte": "^4.2.18", 57 | "svelte-check": "^3.8.5", 58 | "tslib": "^2.6.3", 59 | "typescript": "^5.5.4", 60 | "vite": "^5.3.5", 61 | "vitest": "^2.0.5" 62 | }, 63 | "svelte": "./dist/index.js", 64 | "types": "./dist/index.d.ts", 65 | "type": "module", 66 | "dependencies": { 67 | "@supabase/supabase-js": "^2.45.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /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 | SupaSvelteKit 8 | %sveltekit.head% 9 | 10 | 11 |
%sveltekit.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/components/SupabaseApp.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/auth/Authenticated.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | {#if !$session} 22 | 23 | {:else if $session && $session?.data} 24 | auth.signOut()} /> 25 | {/if} 26 | -------------------------------------------------------------------------------- /src/lib/components/auth/Unauthenticated.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | {#if !$session} 19 | 20 | {:else if !$session.data || !!$session.error} 21 | 22 | {/if} 23 | -------------------------------------------------------------------------------- /src/lib/components/database/Collection.svelte: -------------------------------------------------------------------------------- 1 | 50 | 51 | {#if $store} 52 | 53 | {:else} 54 | 55 | {/if} 56 | -------------------------------------------------------------------------------- /src/lib/components/database/Item.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | {#if $store} 50 | 51 | {:else} 52 | 53 | {/if} 54 | -------------------------------------------------------------------------------- /src/lib/components/function/DelayedFunctionCall.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 | {#if $store} 26 | 27 | {:else} 28 | 29 | {/if} 30 | -------------------------------------------------------------------------------- /src/lib/components/function/FunctionCall.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | {#if $store} 25 | 26 | {:else} 27 | 28 | {/if} 29 | -------------------------------------------------------------------------------- /src/lib/components/function/IntervalFunctionCall.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 | {#if $store} 26 | 27 | {:else} 28 | 29 | {/if} 30 | -------------------------------------------------------------------------------- /src/lib/components/function/ScheduledFunctionCall.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 | {#if $store} 26 | 27 | {:else} 28 | 29 | {/if} 30 | -------------------------------------------------------------------------------- /src/lib/components/realtime/BroadcastChannel.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | {#if $channel} 24 | 25 | {:else} 26 | 27 | {/if} 28 | -------------------------------------------------------------------------------- /src/lib/components/realtime/DbChanges.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | {#if $channel} 27 | 28 | {:else} 29 | 30 | {/if} 31 | -------------------------------------------------------------------------------- /src/lib/components/realtime/RealtimePresence.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 | {#if $store && $status} 40 | 41 | {:else} 42 | 43 | {/if} 44 | -------------------------------------------------------------------------------- /src/lib/components/storage/BucketContext.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/components/storage/BucketFilesList.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | {#if $bucketFiles} 23 | 24 | {:else} 25 | 26 | {/if} 27 | -------------------------------------------------------------------------------- /src/lib/components/storage/BucketsList.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | {#if $buckets} 20 | 21 | {:else} 22 | 23 | {/if} 24 | -------------------------------------------------------------------------------- /src/lib/components/storage/DownloadURL.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 | {#if $downloadUrl} 48 | 49 | {:else} 50 | 51 | {/if} -------------------------------------------------------------------------------- /src/lib/components/storage/Uploader.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | {#if !$fileUploaderStore} 27 | 28 | {:else} 29 | 30 | {/if} -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import SupabaseApp from './components/SupabaseApp.svelte'; 2 | import Authenticated from './components/auth/Authenticated.svelte'; 3 | import Unauthenticated from './components/auth/Unauthenticated.svelte'; 4 | import BucketContext from './components/storage/BucketContext.svelte'; 5 | import BucketFilesList from './components/storage/BucketFilesList.svelte'; 6 | import BucketsList from './components/storage/BucketsList.svelte'; 7 | import DownloadURL from './components/storage/DownloadURL.svelte'; 8 | import Uploader from './components/storage/Uploader.svelte'; 9 | import BroadcastChannel from './components/realtime/BroadcastChannel.svelte'; 10 | import RealtimePresence from './components/realtime/RealtimePresence.svelte'; 11 | import DbChanges from './components/realtime/DbChanges.svelte'; 12 | import Collection from './components/database/Collection.svelte'; 13 | import Item from './components/database/Item.svelte'; 14 | import FunctionCall from './components/function/FunctionCall.svelte'; 15 | import IntervalFunctionCall from './components/function/IntervalFunctionCall.svelte'; 16 | import DelayedFunctionCall from './components/function/DelayedFunctionCall.svelte'; 17 | import ScheduledFunctionCall from './components/function/ScheduledFunctionCall.svelte'; 18 | import { sessionStore } from './stores/session.js'; 19 | import { bucketFilesListStore, bucketsListStore, downloadURLStore, uploadStore } from './stores/bucket.js'; 20 | import { broadcastChannelStore } from './stores/broadcast-channel.js'; 21 | import { presenceStateStore, userStatusStore } from './stores/presence-channel.js'; 22 | import { dbChangesChannelStore } from './stores/db-changes-channel.js'; 23 | import { itemStore, selectStore } from './stores/database.js'; 24 | import { intervalFunctionCallStore, delayedFunctionCallStore, functionCallStore, scheduledFunctionCallStore } from './stores/function.js'; 25 | 26 | export { 27 | SupabaseApp, 28 | Authenticated, 29 | Unauthenticated, 30 | BucketContext, 31 | BucketFilesList, 32 | BucketsList, 33 | DownloadURL, 34 | Uploader, 35 | BroadcastChannel, 36 | RealtimePresence, 37 | DbChanges, 38 | Collection, 39 | Item, 40 | FunctionCall, 41 | IntervalFunctionCall, 42 | DelayedFunctionCall, 43 | ScheduledFunctionCall, 44 | functionCallStore, 45 | intervalFunctionCallStore, 46 | delayedFunctionCallStore, 47 | scheduledFunctionCallStore, 48 | sessionStore, 49 | bucketFilesListStore, 50 | bucketsListStore, 51 | downloadURLStore, 52 | uploadStore, 53 | broadcastChannelStore, 54 | presenceStateStore, 55 | userStatusStore, 56 | dbChangesChannelStore, 57 | itemStore, 58 | selectStore 59 | } -------------------------------------------------------------------------------- /src/lib/stores/broadcast-channel.ts: -------------------------------------------------------------------------------- 1 | import { readable } from 'svelte/store'; 2 | import type { RealtimeChannel, RealtimeClient } from '@supabase/supabase-js'; 3 | 4 | type BroadcastChannelStoreValue = { 5 | data: T | null; 6 | error: Error | null; 7 | }; 8 | 9 | interface BroadcastChannelStore { 10 | subscribe: (cb: (value: BroadcastChannelStoreValue) => void) => void | (() => void); 11 | channel: RealtimeChannel | null; 12 | } 13 | 14 | export function broadcastChannelStore( 15 | realtime: RealtimeClient, 16 | channelName: string, 17 | eventName: string 18 | ): BroadcastChannelStore { 19 | // SSR 20 | if (!globalThis.window) { 21 | const { subscribe } = readable({ data: null, error: null }); 22 | return { 23 | subscribe, 24 | channel: null 25 | }; 26 | } 27 | 28 | //If realtime is not initialized, return a dummy store 29 | if (!realtime) { 30 | console.warn('Realtime is not initialized. Did you forget to create a `supabase` instance?'); 31 | const { subscribe } = readable({ data: null, error: new Error('Realtime is not initialized') }); 32 | return { 33 | subscribe, 34 | channel: null 35 | }; 36 | } 37 | 38 | const channel = realtime.channel(channelName); 39 | 40 | const { subscribe } = readable>({ data: null, error: null }, (set) => { 41 | const subscription = channel 42 | .on('broadcast', { event: eventName }, (payload) => { 43 | set({ data: payload as T, error: null }) 44 | }) 45 | .subscribe((status) => { 46 | switch (status) { 47 | case 'CLOSED': 48 | set({ data: null, error: new Error(`Channel ${channelName} closed`) }); 49 | break; 50 | case 'CHANNEL_ERROR': 51 | set({ data: null, error: new Error(`Channel ${channelName} error`) }); 52 | break; 53 | case 'TIMED_OUT': 54 | set({ 55 | data: null, 56 | error: new Error(`Channel ${channelName} timed out after ${channel.timeout}ms`) 57 | }); 58 | break; 59 | } 60 | }); 61 | 62 | return () => { 63 | subscription.unsubscribe(); 64 | }; 65 | }); 66 | 67 | return { 68 | subscribe, 69 | channel 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/lib/stores/bucket.ts: -------------------------------------------------------------------------------- 1 | import { readable } from 'svelte/store'; 2 | import type { StorageClient, FileObject, Bucket, FileOptions } from '@supabase/storage-js'; 3 | 4 | type BucketFilesList = { 5 | data: FileObject[] | []; 6 | error: Error | null; 7 | }; 8 | 9 | type BucketsList = { 10 | data: Bucket[] | []; 11 | error: Error | null; 12 | }; 13 | 14 | type BucketsDownloadURL = { 15 | data: string | null; 16 | error: Error | null; 17 | }; 18 | 19 | type UploadedFile = { 20 | data: string | null; 21 | error: Error | null; 22 | }; 23 | 24 | interface BucketFilesListStore { 25 | subscribe: (cb: (value: BucketFilesList) => void) => void | (() => void); 26 | } 27 | 28 | export function bucketFilesListStore( 29 | storage: StorageClient, 30 | bucketName: string, 31 | path = '' 32 | ): BucketFilesListStore { 33 | // SSR 34 | if (!globalThis.window) { 35 | const { subscribe } = readable({ data: [], error: null }); 36 | return { 37 | subscribe 38 | }; 39 | } 40 | 41 | //If the storage is not initialized, return a dummy store 42 | if (!storage) { 43 | console.warn('Storage is not initialized. Did you forget to create a `supabase` instance?'); 44 | const { subscribe } = readable({ data: [], error: new Error('Storage is not initialized') }); 45 | return { 46 | subscribe 47 | }; 48 | } 49 | 50 | const { subscribe } = readable({ data: [], error: null }, (set) => { 51 | storage 52 | .from(bucketName) 53 | .list(path) 54 | .then(({ data, error }) => { 55 | set({ data: data ?? [], error }); 56 | }) 57 | .catch((error) => { 58 | set({ data: [], error }); 59 | }); 60 | }); 61 | 62 | return { 63 | subscribe 64 | }; 65 | } 66 | 67 | interface BucketsListStore { 68 | subscribe: (cb: (value: BucketsList) => void) => void | (() => void); 69 | } 70 | 71 | export function bucketsListStore(storage: StorageClient): BucketsListStore { 72 | // SSR 73 | if (!globalThis.window) { 74 | const { subscribe } = readable({ data: [], error: null }); 75 | return { 76 | subscribe 77 | }; 78 | } 79 | 80 | //If the auth is not initialized, return a dummy store 81 | if (!storage) { 82 | console.warn('Storage is not initialized. Did you forget to create a `supabase` instance?'); 83 | const { subscribe } = readable({ data: [], error: new Error('Storage is not initialized') }); 84 | return { 85 | subscribe 86 | }; 87 | } 88 | 89 | const { subscribe } = readable({ data: [], error: null }, (set) => { 90 | storage 91 | .listBuckets() 92 | .then(({ data, error }) => { 93 | set({ data: data ?? [], error }); 94 | }) 95 | .catch((error) => { 96 | set({ data: [], error }); 97 | }); 98 | }); 99 | 100 | return { 101 | subscribe 102 | }; 103 | } 104 | 105 | interface DownloadURLStore { 106 | subscribe: (cb: (value: BucketsDownloadURL) => void) => void | (() => void); 107 | } 108 | 109 | /** 110 | * Download URL Store 111 | * @param storage Supabase Storage Client 112 | * @param bucket Bucket name 113 | * @param path File path 114 | * @param validity Validity in seconds 115 | * @returns A store with the download URL 116 | */ 117 | export function downloadURLStore( 118 | storage: StorageClient, 119 | bucket: string, 120 | path: string, 121 | validity = 60 122 | ): DownloadURLStore { 123 | // SSR 124 | if (!globalThis.window) { 125 | const { subscribe } = readable({ data: null, error: null }); 126 | return { 127 | subscribe 128 | }; 129 | } 130 | 131 | //If the auth is not initialized, return a dummy store 132 | if (!storage) { 133 | console.warn('Storage is not initialized. Did you forget to create a `supabase` instance?'); 134 | const { subscribe } = readable({ data: null, error: new Error('Storage is not initialized') }); 135 | return { 136 | subscribe 137 | }; 138 | } 139 | 140 | const { subscribe } = readable({ data: null, error: null }, (set) => { 141 | storage 142 | .from(bucket) 143 | .createSignedUrl(path, validity) 144 | .then(({ data, error }) => { 145 | set({ data: data?.signedUrl ?? null, error }); 146 | }) 147 | .catch((error) => { 148 | set({ data: null, error }); 149 | }); 150 | }); 151 | 152 | return { 153 | subscribe 154 | }; 155 | } 156 | 157 | export interface UploaderStore { 158 | subscribe: (cb: (value: UploadedFile) => void) => void | (() => void); 159 | } 160 | 161 | export function uploadStore( 162 | storage: StorageClient, 163 | bucketName: string, 164 | path: string, 165 | file?: File | null, 166 | options?: FileOptions 167 | ): UploaderStore { 168 | // SSR 169 | if (!globalThis.window) { 170 | const { subscribe } = readable({ data: null, error: null }); 171 | return { 172 | subscribe 173 | }; 174 | } 175 | 176 | //If the auth is not initialized, return a dummy store 177 | if (!storage) { 178 | console.warn('Storage is not initialized. Did you forget to create a `supabase` instance?'); 179 | const { subscribe } = readable({ data: null, error: new Error('Storage is not initialized') }); 180 | return { 181 | subscribe 182 | }; 183 | } 184 | 185 | if (!file) { 186 | const { subscribe } = readable({ data: null, error: new Error('No file provided') }); 187 | return { 188 | subscribe 189 | }; 190 | } 191 | 192 | const { subscribe } = readable({ data: null, error: null }, (set) => { 193 | storage 194 | .from(bucketName) 195 | .upload(path + file.name, file, options) 196 | .then(({ data, error }) => { 197 | set({ data: data?.path ?? null, error }); 198 | }) 199 | .catch((error) => { 200 | set({ data: null, error }); 201 | }); 202 | }); 203 | 204 | return { 205 | subscribe 206 | }; 207 | } 208 | -------------------------------------------------------------------------------- /src/lib/stores/database.ts: -------------------------------------------------------------------------------- 1 | import { readable, writable, type Writable } from 'svelte/store'; 2 | import type {PostgrestSingleResponse, SupabaseClient } from '@supabase/supabase-js'; 3 | 4 | export type DbChangeEventTypes = "INSERT" | "UPDATE" | "DELETE" | "*"; 5 | 6 | type ArrayOrSingle = T[] | T | null; 7 | 8 | type DbValue = { 9 | data: ArrayOrSingle; 10 | error: Error | null; 11 | }; 12 | 13 | interface DbItemsStore { 14 | subscribe: (cb: (value: DbValue) => void) => void | (() => void); 15 | delete: (deleteCb: (data: T) => boolean) => void; 16 | add: (data: T) => void; 17 | upgrade: (updateCb: (data: T) => T) => void; 18 | } 19 | 20 | export function selectStore( 21 | client: SupabaseClient, 22 | table: string, 23 | query: string, 24 | head: boolean, 25 | count: 'exact' | 'planned' | 'estimated' | undefined 26 | ): DbItemsStore { 27 | return dbStore(client, () => { 28 | const queryBuilder = client.from(table); 29 | 30 | return writable>({ data: null, error: null }, (set) => { 31 | queryBuilder 32 | .select(query, { head, count }) 33 | .then((data: PostgrestSingleResponse) => { 34 | set({ data: Array.isArray(data.data) ? data.data as T[] : data.data as T, error: null }); 35 | }, (error) => { 36 | set({ data: null, error }); 37 | }); 38 | }); 39 | }) 40 | } 41 | 42 | export function itemStore( 43 | client: SupabaseClient, 44 | table: string, 45 | refKey: string, 46 | refValue: unknown | null 47 | ): DbItemsStore { 48 | return dbStore(client, () => { 49 | const queryBuilder = client.from(table); 50 | 51 | return writable>({ data: null, error: null }, (set) => { 52 | queryBuilder 53 | .select() 54 | .eq(refKey, refValue) 55 | .then((data: PostgrestSingleResponse) => { 56 | set({ data: Array.isArray(data.data) ? data.data as T[] : data.data as T, error: null }); 57 | }, (error) => { 58 | set({ data: null, error }); 59 | }); 60 | }); 61 | }) 62 | } 63 | 64 | 65 | 66 | export function dbStore( 67 | client: SupabaseClient, 68 | writableStoreBuilder: () => Writable> 69 | ): DbItemsStore { 70 | // SSR 71 | if (!globalThis.window) { 72 | const { subscribe } = readable({ data: null, error: null }); 73 | return { 74 | subscribe, 75 | delete: defaultCb, 76 | add: defaultCb, 77 | upgrade: defaultCb 78 | }; 79 | } 80 | 81 | //If supabase is not initialized, return a dummy store 82 | if (!client) { 83 | console.warn('Client is not initialized. Did you forget to create a `supabase` instance?'); 84 | const { subscribe } = readable({ data: null, error: new Error('Client is not initialized') }); 85 | return { 86 | subscribe, 87 | delete: defaultCb, 88 | add: defaultCb, 89 | upgrade: defaultCb 90 | }; 91 | } 92 | 93 | const { subscribe, update } = writableStoreBuilder(); 94 | 95 | return { 96 | subscribe, 97 | add: (data: T) => { 98 | update((state) => ({ 99 | data: insertIntoState(state.data, data), 100 | error: null 101 | })); 102 | }, 103 | upgrade(updateCb: (data: T) => T) { 104 | update((state) => ({ 105 | data: updateState(state.data, updateCb), 106 | error: null 107 | })); 108 | }, 109 | delete: (deleteCb: (data: T) => boolean) => { 110 | update((state) => ({ 111 | data: deleteFromState(state.data, deleteCb), 112 | error: null 113 | })); 114 | } 115 | }; 116 | } 117 | 118 | function defaultCb(){return} 119 | 120 | function deleteFromState(state: ArrayOrSingle, deleteCb: (data: T) => boolean): T | T[] | null { 121 | if (Array.isArray(state)) { 122 | const data = state as T[]; 123 | return data.filter((d: T) => !deleteCb(d)); 124 | } else if (deleteCb(state as T)) { 125 | return null; 126 | } 127 | return state; 128 | } 129 | 130 | function insertIntoState(state: ArrayOrSingle, data: T): T[] | T { 131 | if (Array.isArray(state)) { 132 | return [...state, data]; 133 | } 134 | return data; 135 | } 136 | 137 | function updateState(state: ArrayOrSingle, updateCb: (data: T) => T): ArrayOrSingle { 138 | if (Array.isArray(state)) { 139 | const data = state as T[]; 140 | return data.map(updateCb); 141 | } else if (state) { 142 | return updateCb(state as T); 143 | } 144 | return state; 145 | } -------------------------------------------------------------------------------- /src/lib/stores/db-changes-channel.ts: -------------------------------------------------------------------------------- 1 | import { readable } from 'svelte/store'; 2 | import type { RealtimeChannel, RealtimeClient, RealtimePostgresChangesFilter, RealtimePostgresChangesPayload } from '@supabase/supabase-js'; 3 | 4 | export type DbChangeEventTypes = "INSERT" | "UPDATE" | "DELETE" | "*"; 5 | 6 | type DbChangesStoreValue> = { 7 | data: RealtimePostgresChangesPayload | null; 8 | error: Error | null; 9 | }; 10 | 11 | interface DbChangesChannelStore> { 12 | subscribe: (cb: (value: DbChangesStoreValue) => void) => void | (() => void); 13 | channel: RealtimeChannel | null; 14 | } 15 | 16 | export function dbChangesChannelStore, EventType extends DbChangeEventTypes>( 17 | realtime: RealtimeClient, 18 | channelName: string, 19 | event: EventType, 20 | schema: string, 21 | table: string | null, 22 | filter: string | null 23 | ): DbChangesChannelStore { 24 | // SSR 25 | if (!globalThis.window) { 26 | const { subscribe } = readable({ data: null, error: null }); 27 | return { 28 | subscribe, 29 | channel: null 30 | }; 31 | } 32 | 33 | //If realtime is not initialized, return a dummy store 34 | if (!realtime) { 35 | console.warn('Realtime is not initialized. Did you forget to create a `supabase` instance?'); 36 | const { subscribe } = readable({ data: null, error: new Error('Realtime is not initialized') }); 37 | return { 38 | subscribe, 39 | channel: null 40 | }; 41 | } 42 | 43 | let params: Partial> = { event, schema }; 44 | 45 | if(table) { 46 | params = { ...params, table }; 47 | } 48 | if(filter) { 49 | params = { ...params, filter }; 50 | } 51 | 52 | const channel = realtime.channel(channelName); 53 | 54 | const { subscribe } = readable>({ data: null, error: null }, (set) => { 55 | const subscription = channel 56 | .on('postgres_changes', params, (payload: RealtimePostgresChangesPayload) => { 57 | set({ data: payload, error: null }) 58 | }) 59 | .subscribe((status) => { 60 | switch (status) { 61 | case 'CLOSED': 62 | case 'CHANNEL_ERROR': 63 | case 'TIMED_OUT': 64 | set({ 65 | data: null, 66 | error: new Error(`Channel error: ${channelName} - ${status}`) 67 | }); 68 | break; 69 | } 70 | }); 71 | 72 | return () => { 73 | subscription.unsubscribe(); 74 | }; 75 | }); 76 | 77 | return { 78 | subscribe, 79 | channel 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/lib/stores/function.ts: -------------------------------------------------------------------------------- 1 | import { readable, writable } from 'svelte/store'; 2 | import type { FunctionsClient } from '@supabase/functions-js'; 3 | 4 | type Payload = 5 | | string 6 | | File 7 | | Blob 8 | | ArrayBuffer 9 | | FormData 10 | | ReadableStream 11 | | Record 12 | | undefined 13 | 14 | type FunctionStoreValue = { 15 | data: T | null; 16 | error: Error | null; 17 | }; 18 | 19 | interface FunctionStore { 20 | subscribe: (cb: (value: FunctionStoreValue) => void) => void | (() => void); 21 | refresh: () => Promise; 22 | } 23 | 24 | interface IntervalFunctionStore { 25 | subscribe: (cb: (value: FunctionStoreValue) => void) => void | (() => void); 26 | stop: () => void; 27 | } 28 | 29 | interface DelayedFunctionStore { 30 | subscribe: (cb: (value: FunctionStoreValue) => void) => void | (() => void); 31 | } 32 | 33 | const defaultCb = () => { 34 | return Promise.resolve(); 35 | }; 36 | 37 | function functionCall( 38 | functionClient: FunctionsClient, 39 | functionName: string, 40 | payload: Payload, 41 | headers: Record | undefined, 42 | updateCb: (payload: { data: T | null; error: Error }) => void 43 | ) { 44 | return functionClient 45 | .invoke(functionName, { body: payload, headers }) 46 | .then(({ data, error }) => { 47 | updateCb({ data, error }); 48 | }) 49 | .catch((error) => { 50 | updateCb({ data: null, error }); 51 | }); 52 | } 53 | 54 | export function functionCallStore( 55 | functionClient: FunctionsClient, 56 | functionName: string, 57 | headers: Record | undefined, 58 | payload: Payload, 59 | ): FunctionStore { 60 | // SSR 61 | if (!globalThis.window) { 62 | const { subscribe } = writable({ data: null, error: null }); 63 | return { 64 | subscribe, 65 | refresh: defaultCb 66 | }; 67 | } 68 | 69 | //If function is not initialized, return a dummy store 70 | if (!functionClient) { 71 | console.warn('Function is not initialized. Did you forget to create a `supabase` instance?'); 72 | const { subscribe } = writable({ data: null, error: new Error('Function is not initialized') }); 73 | return { 74 | subscribe, 75 | refresh: defaultCb 76 | }; 77 | } 78 | 79 | const { subscribe, update } = writable>( 80 | { data: null, error: null }, 81 | (set) => { 82 | functionCall(functionClient, functionName, payload, headers, set); 83 | } 84 | ); 85 | 86 | return { 87 | subscribe, 88 | refresh: () => { 89 | return functionCall( 90 | functionClient, 91 | functionName, 92 | payload, 93 | headers, 94 | (payload: { data: T | null; error: Error }) => 95 | update(() => ({ data: payload.data, error: payload.error })) 96 | ); 97 | } 98 | }; 99 | } 100 | 101 | export function intervalFunctionCallStore( 102 | functionClient: FunctionsClient, 103 | functionName: string, 104 | interval: number, 105 | headers: Record | undefined, 106 | payload: Payload, 107 | ): IntervalFunctionStore { 108 | const functionStore = functionCallStore(functionClient, functionName, headers, payload); 109 | 110 | const intervalId = setInterval(() => { 111 | functionStore.refresh(); 112 | }, interval); 113 | 114 | return { 115 | subscribe: (cb: (value: FunctionStoreValue) => void) => { 116 | functionStore.subscribe(cb); 117 | 118 | return () => { 119 | clearInterval(intervalId); 120 | }; 121 | }, 122 | stop: () => { 123 | clearInterval(intervalId); 124 | } 125 | }; 126 | } 127 | 128 | export function delayedFunctionCallStore( 129 | functionClient: FunctionsClient, 130 | functionName: string, 131 | delay: number, 132 | headers: Record | undefined, 133 | payload: Payload, 134 | ): DelayedFunctionStore { 135 | // SSR 136 | if (!globalThis.window) { 137 | const { subscribe } = writable({ data: null, error: null }); 138 | return { 139 | subscribe, 140 | }; 141 | } 142 | 143 | //If function is not initialized, return a dummy store 144 | if (!functionClient) { 145 | console.warn('Function is not initialized. Did you forget to create a `supabase` instance?'); 146 | const { subscribe } = writable({ data: null, error: new Error('Function is not initialized') }); 147 | return { 148 | subscribe, 149 | }; 150 | } 151 | 152 | const { subscribe } = readable>( 153 | { data: null, error: null }, 154 | (set) => { 155 | const timeoutId = setTimeout(() => { 156 | functionCall(functionClient, functionName, payload, headers, set); 157 | clearTimeout(timeoutId); 158 | }, delay); 159 | 160 | return () => { 161 | clearTimeout(timeoutId); 162 | }; 163 | } 164 | ); 165 | 166 | return { 167 | subscribe, 168 | }; 169 | } 170 | 171 | export function scheduledFunctionCallStore( 172 | functionClient: FunctionsClient, 173 | functionName: string, 174 | date: Date, 175 | headers: Record | undefined, 176 | payload: Payload, 177 | ): DelayedFunctionStore { 178 | const delay = date.getTime() - Date.now(); 179 | return delayedFunctionCallStore(functionClient, functionName, delay, headers, payload); 180 | } -------------------------------------------------------------------------------- /src/lib/stores/presence-channel.ts: -------------------------------------------------------------------------------- 1 | import { readable, writable } from 'svelte/store'; 2 | import type { RealtimeChannel, RealtimeChannelOptions, RealtimeClient, RealtimePresenceState } from '@supabase/supabase-js'; 3 | 4 | type PresenceStateStoreValue> = { 5 | data: RealtimePresenceState | null; 6 | error: Error | null; 7 | }; 8 | 9 | interface PresenceChannelStore> { 10 | subscribe: (cb: (value: PresenceStateStoreValue) => void) => void | (() => void); 11 | channel: RealtimeChannel | null; 12 | } 13 | 14 | export function presenceStateStore>( 15 | realtime: RealtimeClient, 16 | channelName: string, 17 | channelOptions: RealtimeChannelOptions | undefined = undefined, 18 | userStatus: T = {} as T 19 | ): PresenceChannelStore { 20 | // SSR 21 | if (!globalThis.window) { 22 | const { subscribe } = readable({ data: null, error: null }); 23 | return { 24 | subscribe, 25 | channel: null 26 | }; 27 | } 28 | 29 | //If realtime is not initialized, return a dummy store 30 | if (!realtime) { 31 | console.warn('Realtime is not initialized. Did you forget to create a `supabase` instance?'); 32 | const { subscribe } = readable({ data: null, error: new Error('Realtime is not initialized') }); 33 | return { 34 | subscribe, 35 | channel: null 36 | }; 37 | } 38 | 39 | const channel = realtime.channel(channelName, channelOptions); 40 | 41 | const { subscribe } = readable>({ data: null, error: null }, (set) => { 42 | const subscription = channel 43 | .on('presence', { event: 'sync' }, () => { 44 | set({ data: channel.presenceState(), error: null }); 45 | }) 46 | .subscribe((status) => { 47 | switch (status) { 48 | case 'SUBSCRIBED': 49 | channel.track(userStatus); 50 | break; 51 | case 'CLOSED': 52 | case 'CHANNEL_ERROR': 53 | case 'TIMED_OUT': 54 | set({ data: null, error: new Error(`Channel ${channelName} error: ${status}`) }); 55 | break; 56 | } 57 | }); 58 | 59 | return () => { 60 | subscription.unsubscribe(); 61 | }; 62 | }); 63 | 64 | return { 65 | subscribe, 66 | channel 67 | }; 68 | } 69 | 70 | type UserStatusStoreValue> = { 71 | data: T | null; 72 | error: Error | null; 73 | }; 74 | 75 | interface UserStatusStore> { 76 | subscribe: (cb: (value: UserStatusStoreValue) => void) => void | (() => void); 77 | updateStatus: (status: T) => void; 78 | } 79 | 80 | export function userStatusStore>( 81 | channel: RealtimeChannel, 82 | userStatus: T = {} as T 83 | ): UserStatusStore { 84 | // SSR 85 | if (!globalThis.window) { 86 | const { subscribe } = readable({ data: null, error: null }); 87 | return { 88 | subscribe, 89 | updateStatus: () => { 90 | throw new Error('Not implemented'); 91 | } 92 | }; 93 | } 94 | 95 | //If channel is not initialized, return a dummy store 96 | if (!channel) { 97 | console.warn('Channel is not initialized. Did you forget to create a `channel` instance?'); 98 | const { subscribe } = readable({ data: null, error: new Error('Channel is not initialized') }); 99 | return { 100 | subscribe, 101 | updateStatus: () => { 102 | throw new Error('Not implemented'); 103 | } 104 | }; 105 | } 106 | 107 | const { subscribe, update } = writable>( 108 | { data: userStatus, error: null } 109 | ); 110 | 111 | return { 112 | subscribe, 113 | updateStatus: (status: T) => { 114 | update((previous) => { 115 | if (!status) { 116 | return previous; 117 | } 118 | const newValues = {...previous.data, ...status}; 119 | channel.track(newValues); 120 | return { data: newValues, error: null }; 121 | }); 122 | } 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /src/lib/stores/session.ts: -------------------------------------------------------------------------------- 1 | import { readable } from 'svelte/store'; 2 | import type { SupabaseAuthClient } from '@supabase/supabase-js/dist/module/lib/SupabaseAuthClient.js'; 3 | import type { Session } from '@supabase/supabase-js'; 4 | 5 | type SessionStoreValue = { 6 | data: Session | null; 7 | error: Error | null; 8 | }; 9 | 10 | interface SessionStore { 11 | subscribe: (cb: (value: SessionStoreValue) => void) => void | (() => void); 12 | } 13 | 14 | export function sessionStore(auth: SupabaseAuthClient): SessionStore { 15 | 16 | // SSR 17 | if (!globalThis.window) { 18 | const { subscribe } = readable({ data: null, error: null }); 19 | return { 20 | subscribe 21 | }; 22 | } 23 | 24 | //If the auth is not initialized, return a dummy store 25 | if (!auth) { 26 | console.warn( 27 | 'Auth is not initialized. Did you forget to create a `supabase` instance?' 28 | ); 29 | const { subscribe } = readable({ data: null, error: new Error('Auth is not initialized') }); 30 | return { 31 | subscribe 32 | }; 33 | } 34 | 35 | const { subscribe } = readable({data: null, error: null}, (set) => { 36 | auth.getSession() 37 | .then((session) => { 38 | set({data: session?.data.session ?? null, error: session?.error ?? null}); 39 | }) 40 | .catch((error) => { 41 | set({data: null, error}); 42 | }); 43 | 44 | const unsubscribe = auth.onAuthStateChange((event, session) => { 45 | set({data: session ?? null, error: null}); 46 | }).data?.subscription?.unsubscribe; 47 | 48 | return () => { 49 | unsubscribe?.(); 50 | } 51 | }); 52 | 53 | return { 54 | subscribe 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/stores/supabase-sdk.ts: -------------------------------------------------------------------------------- 1 | import type { SupabaseAuthClient } from "@supabase/supabase-js/dist/module/lib/SupabaseAuthClient.js"; 2 | import type { StorageClient } from "@supabase/storage-js"; 3 | import { getContext, setContext } from "svelte"; 4 | import type { RealtimeClient, SupabaseClient } from "@supabase/supabase-js"; 5 | import type { FunctionsClient } from '@supabase/functions-js'; 6 | 7 | export interface SupabaseContext { 8 | client: SupabaseClient; 9 | auth?: SupabaseAuthClient; 10 | storage?: StorageClient; 11 | realtime?: RealtimeClient; 12 | function?: FunctionsClient; 13 | } 14 | 15 | export interface BucketContext { 16 | bucketName: string; 17 | path: string; 18 | } 19 | 20 | export const contextKey = 'supabaseContext'; 21 | export const bucketContextKey = 'bucketContext'; 22 | 23 | export function setSupabaseContext(sdks: SupabaseContext) { 24 | setContext(contextKey, sdks); 25 | } 26 | 27 | export function getSupabaseContext(): SupabaseContext { 28 | return getContext(contextKey); 29 | } 30 | 31 | export function setBucketContext(bucketContext: BucketContext) { 32 | setContext(bucketContextKey, bucketContext); 33 | } 34 | 35 | export function getBucketContext(): BucketContext { 36 | return getContext(bucketContextKey); 37 | } 38 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 |

Welcome to SupaSvelte

2 |

This page is intended to give a quick access to test routes

3 | 9 | -------------------------------------------------------------------------------- /src/routes/auth/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 |

Sign in to continue

12 | 13 |
14 | 15 | 16 |

Welcome {session?.user?.identities?.[0]?.identity_data?.email}

17 | 18 |
-------------------------------------------------------------------------------- /src/routes/constants.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SUPABASE_KEY, PUBLIC_SUPABASE_URL, PUBLIC_TEST_USER_EMAIL, PUBLIC_TEST_USER_PASSWORD } from "$env/static/public"; 2 | import { createClient } from "@supabase/supabase-js"; 3 | 4 | export const supabaseUrl = PUBLIC_SUPABASE_URL; 5 | export const supabaseKey = PUBLIC_SUPABASE_KEY; 6 | 7 | export const supabase = createClient(supabaseUrl, supabaseKey); 8 | 9 | if(process.env.NODE_ENV === "development") { 10 | supabase.auth.signUp({email: PUBLIC_TEST_USER_EMAIL, password: PUBLIC_TEST_USER_PASSWORD}).then(account => { 11 | console.log("Test user created.", account); 12 | }); 13 | 14 | // Upload a sample file to the bucket 15 | const file = new File(['Hello, world!'], 'hello.txt', { type: 'text/plain' }); 16 | supabase.storage.from("test-bucket").upload('public/hello.txt', file) 17 | .catch(error => { 18 | console.error("Error uploading file:", error); 19 | }) 20 | .then(data => { 21 | console.log("File uploaded successfully:", data); 22 | }); 23 | } -------------------------------------------------------------------------------- /src/routes/database/+page.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | {#if !payload} 13 |

Loading...

14 | {:else if error} 15 |

{error.message}

16 | {:else} 17 |

Count: {payload.length}

18 | {#each payload as entry} 19 |

ID: {entry.id} Created at {entry.created_at}

20 | {/each} 21 | {/if} 22 |
23 |

Loading...

24 |
25 | 26 | 31 | 32 | 38 | 39 | 45 | 46 | 49 |
50 | 51 | 52 |

DB Changes

53 |

Last change received: {payload?.eventType ?? "none"}

54 |
55 | 56 | {#key realtimeItemId} 57 |

Realtime single item {realtimeItemId}

58 | 59 |

{JSON.stringify(payload)}

60 |
61 | {/key} -------------------------------------------------------------------------------- /src/routes/function/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 |
14 | {#if payload} 15 |

The time is {payload.time}

16 | {:else if error} 17 |

There was an error: {JSON.stringify(error)}

18 | {:else} 19 |

Loading...

20 | {/if} 21 |
22 |
23 | 24 | 25 |
26 | {#if payload} 27 |

The time is {payload.time}

28 | {:else if error} 29 |

There was an error: {JSON.stringify(error)}

30 | {:else} 31 |

Loading...

32 | {/if} 33 |
34 |
35 | 36 | 37 |
38 | {#if payload} 39 |

The time is {payload.time}

40 | {:else if error} 41 |

There was an error: {JSON.stringify(error)}

42 | {:else} 43 |

Loading...

44 | {/if} 45 |
46 |
47 | 48 | 49 |
50 | {#if payload} 51 |

The time is {payload.time}

52 | {:else if error} 53 |

There was an error: {JSON.stringify(error)}

54 | {:else} 55 |

Loading...

56 | {/if} 57 |
58 |
59 | -------------------------------------------------------------------------------- /src/routes/realtime/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 |

Realtime

14 |

Open this page in multiple tabs to see the realtime updates.

15 |

Channel name: {channel?.topic}

16 |

Event name: {eventName}

17 |

Channel started: {!!channel}

18 | 21 |

Last message received: {payload?.message}

22 |
23 | 24 | console.log("New user joined")} on:leave={() => console.log("User left")}> 25 |

Presence

26 |

Channel name: multiplayer

27 | {#if state} 28 |

Users online: {Object.values(state).flatMap(v => v.filter(x => x.status === "online")).length}

29 |
    30 | {#each Object.values(state).flatMap(v => v.filter(x => x.status === "online")) as user} 31 |
  • {user.userName}
  • 32 | {/each} 33 |
34 | 35 | 36 | {:else} 37 |

Users online: 0

38 | {/if} 39 |
-------------------------------------------------------------------------------- /src/routes/storage/+page.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 |

Buckets

26 | {#if buckets !== null} 27 | {#each buckets as bucket} 28 |

{bucket.name}

29 | 30 | {#if error !== null} 31 |
{error}
32 | {/if} 33 |
    34 | {#each bucketFiles as file} 35 |
  • 36 | 37 | {#if error !== null} 38 |
    {error}
    39 | {/if} 40 | {#if url !== null} 41 | {file.name} 42 | {/if} 43 |
    44 |
  • 45 | {/each} 46 |
47 |
48 | {/each} 49 | {/if} 50 | {#if error !== null} 51 |
{error}
52 | {/if} 53 |
54 | 55 | 56 | file = e?.currentTarget?.files?.[0] ?? null}/> 57 | 58 | 59 | 60 | {#if file} 61 | 62 | {#if error} 63 |
{error.message}
64 | {/if} 65 | {#if uploadedFile} 66 |
{uploadedFile}
67 | {/if} 68 |
69 | {/if} 70 |
-------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFrenchFries/supasveltekit/3403a9a34c72d0697aa92997a8b9c69e80d8189e/static/favicon.png -------------------------------------------------------------------------------- /supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | -------------------------------------------------------------------------------- /supabase/config.toml: -------------------------------------------------------------------------------- 1 | # A string used to distinguish different Supabase projects on the same host. Defaults to the 2 | # working directory name when running `supabase init`. 3 | project_id = "supasveltekit" 4 | 5 | [api] 6 | # Port to use for the API URL. 7 | port = 54321 8 | # Schemas to expose in your API. Tables, views and stored procedures in this schema will get API 9 | # endpoints. public and storage are always included. 10 | schemas = ["public", "storage", "graphql_public"] 11 | # Extra schemas to add to the search_path of every request. public is always included. 12 | extra_search_path = ["public", "extensions"] 13 | # The maximum number of rows returns from a view, table, or stored procedure. Limits payload size 14 | # for accidental or malicious requests. 15 | max_rows = 1000 16 | 17 | [db] 18 | # Port to use for the local database URL. 19 | port = 54322 20 | # Port used by db diff command to initialise the shadow database. 21 | shadow_port = 54320 22 | # The database major version to use. This has to be the same as your remote database's. Run `SHOW 23 | # server_version;` on the remote database to check. 24 | major_version = 15 25 | 26 | [db.pooler] 27 | enabled = false 28 | port = 54329 29 | pool_mode = "transaction" 30 | default_pool_size = 20 31 | max_client_conn = 100 32 | 33 | [studio] 34 | enabled = true 35 | # Port to use for Supabase Studio. 36 | port = 54323 37 | # External URL of the API server that frontend connects to. 38 | api_url = "http://localhost" 39 | 40 | # Email testing server. Emails sent with the local dev setup are not actually sent - rather, they 41 | # are monitored, and you can view the emails that would have been sent from the web interface. 42 | [inbucket] 43 | enabled = true 44 | # Port to use for the email testing server web interface. 45 | port = 54324 46 | # Uncomment to expose additional ports for testing user applications that send emails. 47 | # smtp_port = 54325 48 | # pop3_port = 54326 49 | 50 | [storage] 51 | # The maximum file size allowed (e.g. "5MB", "500KB"). 52 | file_size_limit = "50MiB" 53 | 54 | [auth] 55 | # The base URL of your website. Used as an allow-list for redirects and for constructing URLs used 56 | # in emails. 57 | site_url = "http://localhost:3000" 58 | # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. 59 | additional_redirect_urls = ["https://localhost:3000"] 60 | # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). 61 | jwt_expiry = 3600 62 | # If disabled, the refresh token will never expire. 63 | enable_refresh_token_rotation = true 64 | # Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. 65 | # Requires enable_refresh_token_rotation = true. 66 | refresh_token_reuse_interval = 10 67 | # Allow/disallow new user signups to your project. 68 | enable_signup = true 69 | 70 | [auth.email] 71 | # Allow/disallow new user signups via email to your project. 72 | enable_signup = true 73 | # If enabled, a user will be required to confirm any email change on both the old, and new email 74 | # addresses. If disabled, only the new email is required to confirm. 75 | double_confirm_changes = true 76 | # If enabled, users need to confirm their email address before signing in. 77 | enable_confirmations = false 78 | 79 | # Uncomment to customize email template 80 | # [auth.email.template.invite] 81 | # subject = "You have been invited" 82 | # content_path = "./supabase/templates/invite.html" 83 | 84 | [auth.sms] 85 | # Allow/disallow new user signups via SMS to your project. 86 | enable_signup = true 87 | # If enabled, users need to confirm their phone number before signing in. 88 | enable_confirmations = false 89 | 90 | # Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. 91 | [auth.sms.twilio] 92 | enabled = false 93 | account_sid = "" 94 | message_service_sid = "" 95 | # DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: 96 | auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" 97 | 98 | # Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, 99 | # `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`, 100 | # `twitter`, `slack`, `spotify`, `workos`, `zoom`. 101 | [auth.external.apple] 102 | enabled = false 103 | client_id = "" 104 | # DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: 105 | secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" 106 | # Overrides the default auth redirectUrl. 107 | redirect_uri = "" 108 | # Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, 109 | # or any other third-party OIDC providers. 110 | url = "" 111 | 112 | [analytics] 113 | enabled = false 114 | port = 54327 115 | vector_port = 54328 116 | # Configure one of the supported backends: `postgres`, `bigquery` 117 | backend = "postgres" 118 | -------------------------------------------------------------------------------- /supabase/functions/get-time/index.ts: -------------------------------------------------------------------------------- 1 | // Follow this setup guide to integrate the Deno language server with your editor: 2 | // https://deno.land/manual/getting_started/setup_your_environment 3 | // This enables autocomplete, go to definition, etc. 4 | 5 | console.log("Hello from Functions!",Date.now()) 6 | 7 | Deno.serve(async (request) => { 8 | const data = { 9 | time: Date.now(), 10 | } 11 | 12 | return new Response( 13 | JSON.stringify(data), 14 | { headers: { 15 | "Content-Type": "application/json", 16 | 'Access-Control-Allow-Origin': request.headers.get('origin'), 17 | 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type' 18 | } }, 19 | ) 20 | }) 21 | 22 | // To invoke: 23 | // curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/' \ 24 | // --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \ 25 | // --header 'Content-Type: application/json' \ 26 | // --data '{"name":"Functions"}' 27 | -------------------------------------------------------------------------------- /supabase/migrations/20230915110343_init.sql: -------------------------------------------------------------------------------- 1 | -- Create bucket 2 | insert into storage.buckets(id, name) values ('test-bucket', 'test-bucket'); 3 | 4 | -- Create a public select policy for access to buckets, don't try this at home!!!! 5 | create policy "Public Bucket Select" on storage.buckets for select using ( true ); 6 | 7 | -- Create a public select and insert policy for access to test-bucket, don't try this at home!!!! 8 | create policy "Public Object Select" on storage.objects for select using ( bucket_id = 'test-bucket' ); 9 | create policy "Public Object Insert" on storage.objects for insert with check (bucket_id = 'test-bucket' ); 10 | create policy "Public Object Update" on storage.objects for update using (bucket_id = 'test-bucket' ); 11 | -------------------------------------------------------------------------------- /supabase/migrations/20231004214009_test-table.sql: -------------------------------------------------------------------------------- 1 | create table 2 | public.test ( 3 | id bigint generated by default as identity, 4 | created_at timestamp 5 | with 6 | time zone null default now (), 7 | constraint test_pkey primary key (id) 8 | ) tablespace pg_default; 9 | 10 | alter publication supabase_realtime add table test; -------------------------------------------------------------------------------- /supabase/seed.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFrenchFries/supasveltekit/3403a9a34c72d0697aa92997a8b9c69e80d8189e/supabase/seed.sql -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /tests/auth.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, type Page } from '@playwright/test'; 2 | 3 | test.describe.parallel('Auth', () => { 4 | let page: Page; 5 | 6 | test.beforeAll(async ({ browser }) => { 7 | page = await browser.newPage(); 8 | await page.goto('/auth'); 9 | await page.waitForLoadState('networkidle'); 10 | }); 11 | 12 | test.afterAll(async () => { 13 | await page.close(); 14 | }); 15 | 16 | test('Should render contextual button based on auth state', async () => { 17 | await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible(); 18 | await expect(page.getByRole('button', { name: 'Sign Out' })).toBeHidden(); 19 | }); 20 | 21 | test('User can sign in and out', async () => { 22 | await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible(); 23 | await page.getByRole('button', { name: 'Sign In' }).click({ delay: 1000 }); 24 | await expect(page.getByRole('button', { name: 'Sign Out' })).toBeVisible(); 25 | 26 | await page.getByRole('button', { name: 'Sign Out' }).click(); 27 | await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible(); 28 | await expect(page.getByText('Sign in to continue')).toBeVisible(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/buckets.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, type Page } from '@playwright/test'; 2 | 3 | test.describe.parallel('Storage', () => { 4 | let page: Page; 5 | 6 | test.beforeAll(async ({ browser }) => { 7 | page = await browser.newPage(); 8 | await page.goto('/storage'); 9 | await page.waitForLoadState('networkidle'); 10 | }); 11 | 12 | test.beforeEach(async () => { 13 | await page.reload(); 14 | }); 15 | 16 | test.afterAll(async () => { 17 | await page.close(); 18 | }); 19 | 20 | test('should display buckets names', async () => { 21 | await expect(page.getByRole('heading', { name: 'test-bucket' })).toBeDefined(); 22 | }); 23 | 24 | test('should display bucket file names', async () => { 25 | await expect(page.getByTestId('hello.txt')).toBeDefined(); 26 | }); 27 | 28 | test('should display download link', async () => { 29 | await expect(page.getByRole('link', { name: 'hello.txt' })).toBeDefined(); 30 | }); 31 | 32 | test('should create and upload a file and should display error on duplication', async () => { 33 | await expect(page.getByRole('button', { name: 'Create file' })).toBeVisible(); 34 | await page.getByRole('button', { name: 'Create file' }).click(); 35 | await expect(page.getByTestId("upload-filename")).toBeVisible(); 36 | await expect(page.getByTestId("upload-filename")).toContainText("goodbye.txt"); 37 | }); 38 | 39 | test('should display error on duplication', async () => { 40 | await page.getByRole('button', { name: 'Duplicate hello.txt' }).click(); 41 | await expect(page.getByTestId("upload-error")).toBeVisible(); 42 | await expect(page.getByTestId("upload-error")).toContainText("The resource already exists"); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /tests/database.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, type Page } from '@playwright/test'; 2 | 3 | test.describe.serial('Database', () => { 4 | let page: Page; 5 | 6 | test.beforeAll(async ({ browser }) => { 7 | page = await browser.newPage(); 8 | await page.goto('/database'); 9 | await page.waitForLoadState('networkidle'); 10 | }); 11 | 12 | test.afterAll(async () => { 13 | await page.close(); 14 | }); 15 | 16 | test('should list entries and receive realtime updates', async () => { 17 | await page.reload(); 18 | await page.waitForSelector(".items-count"); 19 | const count = +(await (page.getByTestId("items-count").textContent()) ?? '0'); 20 | await page.getByRole('button', { name: 'Insert data in DB' }).click(); 21 | await expect(page.getByTestId("items-count")).toHaveText((count + 1).toString()); 22 | 23 | const createdAt = await page.locator(".items").first().textContent(); 24 | await page.getByRole('button', { name: 'Update data in DB' }).click(); 25 | await expect(page.locator(".items").first()).not.toHaveText(createdAt ?? ''); 26 | 27 | await page.getByRole('button', { name: 'Delete data in DB' }).click(); 28 | await expect(page.getByTestId("items-count")).toHaveText(count.toString()); 29 | }); 30 | 31 | test('should synchronize item in real time', async () => { 32 | await page.waitForSelector(".items-count"); 33 | 34 | await page.getByRole('button', { name: 'Insert data in DB' }).click(); 35 | await expect(page.getByTestId("realtime-item")).toHaveText('[]'); 36 | await page.waitForTimeout(1000); 37 | await page.getByRole('button', { name: 'Activate realtime item' }).click(); 38 | await expect(page.getByTestId("realtime-item")).not.toHaveText('[]'); 39 | 40 | const text = await page.getByTestId("realtime-item").textContent(); 41 | 42 | await page.getByRole('button', { name: 'Update data in DB' }).click(); 43 | await expect(page.getByTestId("realtime-item")).not.toHaveText(text ?? ''); 44 | 45 | await page.getByRole('button', { name: 'Delete data in DB' }).click(); 46 | }); 47 | 48 | test('should listen to db update', async () => { 49 | await page.getByRole('button', { name: 'Insert data in DB' }).click(); 50 | 51 | await expect(page.getByTestId("received-change")).toHaveText("INSERT"); 52 | 53 | await page.getByRole('button', { name: 'Delete data in DB' }).click(); 54 | 55 | }); 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /tests/function.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, type Page } from '@playwright/test'; 2 | 3 | test.describe.parallel('Function', () => { 4 | let page: Page; 5 | 6 | test.beforeAll(async ({ browser }) => { 7 | page = await browser.newPage(); 8 | page.setDefaultTimeout(10000); 9 | await page.goto('/function'); 10 | 11 | await page.waitForLoadState('networkidle'); 12 | }); 13 | 14 | test.afterAll(async () => { 15 | await page.close(); 16 | }); 17 | 18 | test('should call get-time function', async () => { 19 | await expect(page.getByTestId("function-call")).toContainText("The time is"); 20 | }); 21 | 22 | test('should call get-time function repeatedly', async () => { 23 | const content = await page.getByTestId("interval-function-call").textContent(); 24 | await page.waitForTimeout(3000); 25 | await expect(page.getByTestId("interval-function-call")).not.toContainText(content ?? ""); 26 | }); 27 | 28 | test('should call get-time function after delay', async () => { 29 | await expect(page.getByTestId("delayed-function-call")).toContainText("Loading..."); 30 | await page.waitForTimeout(3000); 31 | await expect(page.getByTestId("delayed-function-call")).toContainText("The time is"); 32 | }); 33 | 34 | test('should call get-time function at a scheduled time', async () => { 35 | await expect(page.getByTestId("scheduled-function-call")).toContainText("Loading..."); 36 | await page.waitForTimeout(5000); 37 | await expect(page.getByTestId("scheduled-function-call")).toContainText("The time is"); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/realtime.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, type Page } from '@playwright/test'; 2 | 3 | test.describe.parallel('Realtime', () => { 4 | let page1: Page; 5 | let page2: Page; 6 | 7 | test.beforeAll(async ({ browser }) => { 8 | page1 = await browser.newPage(); 9 | await page1.goto('/realtime'); 10 | page2 = await browser.newPage(); 11 | await page2.goto('/realtime'); 12 | 13 | await page1.waitForLoadState('networkidle'); 14 | await page2.waitForLoadState('networkidle'); 15 | }); 16 | 17 | test.afterAll(async () => { 18 | await page1.close(); 19 | await page2.close(); 20 | }); 21 | 22 | test('should receive broadcast message', async () => { 23 | await page1.getByRole('button', { name: 'Send message' }).click(); 24 | await expect(page2.getByTestId("received-message")).toBeVisible(); 25 | await expect(page2.getByTestId("received-message")).toContainText("Last message received: Hello from any/message!"); 26 | }); 27 | 28 | test('should sync realtime presences', async () => { 29 | await expect(page1.getByTestId("users-online")).toContainText("0"); 30 | await expect(page2.getByTestId("users-online")).toContainText("0"); 31 | 32 | await page1.getByTestId("username").fill("User 1"); 33 | await page1.getByRole('button', { name: 'Set status to online' }).click(); 34 | 35 | await expect(page1.getByTestId("users-online")).toContainText("1"); 36 | await expect(page2.getByTestId("users-online")).toContainText("1"); 37 | 38 | await expect(page1.getByTestId("User 1")).toContainText("User 1"); 39 | await expect(page2.getByTestId("User 1")).toContainText("User 1"); 40 | 41 | await page2.getByTestId("username").fill("User 2"); 42 | await page2.getByRole('button', { name: 'Set status to online' }).click(); 43 | 44 | await expect(page1.getByTestId("users-online")).toContainText("2"); 45 | await expect(page2.getByTestId("users-online")).toContainText("2"); 46 | 47 | await expect(page1.getByTestId("User 2")).toContainText("User 2"); 48 | await expect(page2.getByTestId("User 2")).toContainText("User 2"); 49 | 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /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 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext", 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'] 8 | } 9 | }); 10 | --------------------------------------------------------------------------------