-
Notifications
You must be signed in to change notification settings - Fork 61
[Package] PowerSync Nuxt Module #797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
db1ecce
e5c95ac
3521b82
c58db3c
5771c58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| node_modules | ||
| lib | ||
| dist | ||
| .nuxt | ||
| .output | ||
| *.tsbuildinfo | ||
| .vscode | ||
| .DS_STORE | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| v20.9.0 | ||
| v20 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Self-hosted Environment Configuration | ||
| # Copy this template: `cp .env.template .env` | ||
| # Edit .env and enter your Supabase and PowerSync project details. | ||
|
|
||
| NUXT_PUBLIC_SUPABASE_URL=http://localhost:54321 | ||
| NUXT_PUBLIC_SUPABASE_ANON_KEY=<replace-with-your-anon-key> | ||
| # PowerSync Configuration | ||
| NUXT_PUBLIC_POWERSYNC_URL=http://localhost:6000 | ||
|
|
||
|
|
||
| # If using Powersync Cloud or a Cloud source database uses these | ||
| # Supabase Configuration | ||
| # VITE_SUPABASE_URL=https://<your-project-id>.supabase.co | ||
| # VITE_SUPABASE_ANON_KEY=<replace-with-your-anon-key> | ||
|
|
||
| # PowerSync Configuration | ||
| # VITE_POWERSYNC_URL=https://<your-project-id>.powersync.journeyapps.com | ||
|
|
||
| # Self-hosted PowerSync Configuration | ||
| PS_POSTGRESQL_URI=postgresql://postgres:postgres@supabase_db_powersync:5432/postgres | ||
| PS_SUPABASE_JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long | ||
| PS_API_TOKEN=super-secret | ||
| PS_PORT=6000 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| imports.autoImport=true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # PowerSync + Supabase Nuxt Demo: Todo List | ||
|
|
||
| This is a demo application showcasing PowerSync integration with Nuxt 4 and Supabase. It demonstrates real-time data synchronization for a simple todo list application using PowerSync's official Nuxt module. | ||
|
|
||
| ## Setup Instructions | ||
|
|
||
| Note that this setup guide has minor deviations from the [Supabase + PowerSync integration guide](https://docs.powersync.com/integration-guides/supabase-+-powersync). Below we refer to sections in this guide where relevant. | ||
|
|
||
| ### 1. Install dependencies | ||
|
|
||
| In the repo root directory, use [pnpm](https://pnpm.io/installation) to install dependencies: | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| pnpm build:packages | ||
| ``` | ||
|
|
||
| ### 2. Create project on Supabase and set up Postgres | ||
|
|
||
| This demo app uses Supabase as its Postgres database and backend: | ||
|
|
||
| 1. [Create a new project on the Supabase dashboard](https://supabase.com/dashboard/projects). | ||
| 2. Go to the Supabase SQL Editor for your new project and execute the SQL statements in [`db/seed.sql`](db/seed.sql) to create the database schema, PowerSync replication role, and publication needed for PowerSync. | ||
|
|
||
| **Important:** When connecting PowerSync to your Supabase database, you'll use the `powersync_role` credentials instead of the default Supabase connection string. This role has the necessary replication privileges and bypasses Row Level Security (RLS). | ||
|
|
||
| ### 3. Auth setup | ||
|
|
||
| This app uses Supabase's email/password authentication. | ||
|
|
||
| 1. Go to "Authentication" -> "Providers" in your Supabase dashboard | ||
| 2. Ensure "Email" provider is enabled | ||
| 3. You can disable email confirmation for development by going to "Authentication" -> "Email Auth" and disabling "Confirm email" | ||
|
|
||
| You'll need to create a user account when you first access the application. | ||
|
|
||
| ### 4. Set up PowerSync | ||
|
|
||
| You can use either PowerSync Cloud or self-host PowerSync: | ||
|
|
||
| - **PowerSync Cloud**: [Create a new project on the PowerSync dashboard](https://powersync.journeyapps.com/) and connect it to your Supabase database using the `powersync_role` credentials created in step 2. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: should probably link to https://dashboard.powersync.com/ |
||
| - **Self-hosting**: Follow the [self-hosting guide](https://docs.powersync.com/self-hosting/getting-started) to deploy your own PowerSync instance. | ||
|
|
||
| The sync rules for this demo are provided in [`sync-rules.yaml`](sync-rules.yaml) in this directory. | ||
|
|
||
| ### 5. Set up local environment variables | ||
|
|
||
| Create a `.env` file in this directory with the following variables: | ||
|
|
||
| ```bash | ||
| NUXT_PUBLIC_SUPABASE_URL=your_supabase_url | ||
| NUXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key | ||
| NUXT_PUBLIC_POWERSYNC_URL=your_powersync_instance_url | ||
| ``` | ||
|
|
||
| Replace the values with your actual credentials: | ||
| - Get `NUXT_PUBLIC_SUPABASE_URL` and `NUXT_PUBLIC_SUPABASE_ANON_KEY` from your Supabase project settings under "Project Settings" -> "API" | ||
| - Get `NUXT_PUBLIC_POWERSYNC_URL` from your PowerSync instance (Cloud dashboard or your self-hosted instance URL) | ||
|
|
||
| ### 6. Run the demo app | ||
|
|
||
| In this directory, run the following to start the development server: | ||
|
|
||
| ```bash | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:3000](http://localhost:3000) with your browser to try out the demo. | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| ├── powersync/ | ||
| │ ├── AppSchema.ts # PowerSync schema definition | ||
| │ └── SuperbaseConnector.ts # Supabase connector implementation | ||
| ├── plugins/ | ||
| │ └── powersync.client.ts # PowerSync plugin setup | ||
| ├── pages/ | ||
| │ ├── index.vue # Main todo list page | ||
| │ ├── login.vue # Login page | ||
| │ └── confirm.vue # Auth confirmation page | ||
| ├── components/ | ||
| │ └── AppHeader.vue # Header component | ||
| ├── db/ | ||
| │ └── seed.sql # Database setup SQL | ||
| ├── sync-rules.yaml # PowerSync sync rules | ||
| └── nuxt.config.ts # Nuxt configuration | ||
| ``` | ||
|
|
||
| ## Learn More | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice if this demo had a local Supabase config and quickstart for local development - which can be used to start the demo in only a few commands - like the YJS Demo has here. |
||
|
|
||
| - [PowerSync Documentation](https://docs.powersync.com/) | ||
| - [Supabase Documentation](https://supabase.com/docs) | ||
| - [Nuxt Documentation](https://nuxt.com/) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| export default defineAppConfig({ | ||
| ui: { | ||
| colors: { | ||
| primary: 'indigo', | ||
| neutral: 'stone', | ||
| }, | ||
| input: { | ||
| variants: { | ||
| variant: { | ||
| subtle: 'ring-default bg-elevated/50', | ||
| }, | ||
| }, | ||
| }, | ||
| header: { | ||
| slots: { | ||
| root: 'border-none', | ||
| }, | ||
| }, | ||
| }, | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| <template> | ||
| <UApp> | ||
| <NuxtLayout> | ||
| <NuxtPage /> | ||
| </NuxtLayout> | ||
| </UApp> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| useHead({ | ||
| meta: [{ name: 'viewport', content: 'width=device-width, initial-scale=1' }], | ||
| link: [{ rel: 'icon', href: '/favicon.ico' }], | ||
| htmlAttrs: { | ||
| lang: 'en', | ||
| }, | ||
| }) | ||
|
|
||
| const title = 'PowerSync Playground' | ||
| const description | ||
| = 'Demo of a simple todo list app using PowerSync and Supabase.' | ||
|
|
||
| useSeoMeta({ | ||
| title, | ||
| ogTitle: title, | ||
| description, | ||
| ogDescription: description, | ||
| }) | ||
|
|
||
| const appIsReady = ref(false) | ||
|
|
||
| provide('appIsReady', readonly(appIsReady)) | ||
|
|
||
| const powerSync = usePowerSync() | ||
| const syncStatus = usePowerSyncStatus() | ||
|
|
||
| const user = useSupabaseUser() | ||
| const { logger: powerSyncLogger } = useDiagnosticsLogger() | ||
|
|
||
| watch(user, () => { | ||
| if (user) { | ||
| if (syncStatus.value.hasSynced) { | ||
| powerSyncLogger.log('User is logged in and has synced...', { user: user, syncStatus: syncStatus.value }) | ||
| appIsReady.value = true | ||
| } | ||
| else { | ||
| powerSyncLogger.log('User is logged waiting for first sync...', { user: user, syncStatus: syncStatus.value }) | ||
| powerSync.value.waitForFirstSync().then(() => { | ||
| appIsReady.value = true | ||
| }) | ||
| } | ||
| } | ||
| else { | ||
| powerSyncLogger.log('User is not logged in disconnecting...', { user: user, syncStatus: syncStatus.value }) | ||
| powerSync.value.disconnect() | ||
| appIsReady.value = true | ||
| } | ||
| }, { immediate: true }) | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| @import "tailwindcss"; | ||
| @import "@nuxt/ui"; | ||
|
|
||
| :root { | ||
| --ui-header-height: 40px; | ||
|
|
||
| --ui-container: 100%; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| <script setup lang="ts"> | ||
| const client = useSupabaseClient() | ||
| const user = useSupabaseUser() | ||
| const powerSync = usePowerSync() | ||
|
|
||
| const logout = async () => { | ||
| await powerSync.value.disconnectAndClear() | ||
|
|
||
| await client.auth.signOut() | ||
| navigateTo('/login') | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <UHeader :toggle="false"> | ||
| <template #left> | ||
| <UButton | ||
| variant="link" | ||
| @click="navigateTo('/')" | ||
| > | ||
| <img | ||
| src="https://cdn.prod.website-files.com/67eea61902e19994e7054ea0/67f910109a12edc930f8ffb6_powersync-icon.svg" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this link copied from some site, or, where is it managed? Could we use an asset for this? |
||
| alt="Powersync" | ||
| class="size-10 inline-flex" | ||
| > | ||
| </UButton> | ||
| </template> | ||
|
|
||
| <template #right> | ||
| <UColorModeButton variant="link" /> | ||
|
|
||
| <UButton | ||
| v-if="user" | ||
| variant="link" | ||
| class="cursor-pointer" | ||
| color="neutral" | ||
| @click="logout" | ||
| > | ||
| Logout | ||
| </UButton> | ||
| </template> | ||
| </UHeader> | ||
| </template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| -- Past this into your Superbase SQL Editor | ||
|
|
||
| -- TODO change this if changing the DB connection name | ||
| -- connect postgres; | ||
| -- Create tables | ||
|
|
||
| CREATE TABLE IF NOT EXISTS public.tasks( | ||
| id uuid NOT NULL DEFAULT gen_random_uuid(), | ||
| created_at timestamp with time zone NOT NULL DEFAULT now(), | ||
| completed_at timestamp with time zone NULL, | ||
| description text NOT NULL, | ||
| completed boolean NOT NULL DEFAULT FALSE, | ||
| user_id uuid NOT NULL, | ||
| CONSTRAINT tasks_pkey PRIMARY KEY (id) | ||
| ); | ||
|
|
||
| -- Create a role/user with replication privileges for PowerSync | ||
| CREATE ROLE powersync_role WITH REPLICATION BYPASSRLS LOGIN PASSWORD 'postgres_12345'; | ||
| -- Set up permissions for the newly created role | ||
| -- Read-only (SELECT) access is required | ||
| GRANT SELECT ON ALL TABLES IN SCHEMA public TO powersync_role; | ||
|
|
||
| -- Optionally, grant SELECT on all future tables (to cater for schema additions) | ||
| ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO powersync_role; | ||
|
|
||
|
|
||
| -- Create publication for PowerSync tables | ||
| CREATE PUBLICATION powersync FOR ALL TABLES; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // @ts-check | ||
| import withNuxt from './.nuxt/eslint.config.mjs' | ||
|
|
||
| export default withNuxt({ | ||
| rules: { | ||
| '@typescript-eslint/no-explicit-any': 'off', | ||
| 'nuxt/nuxt-config-keys-order': 'off', | ||
| }, | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <template> | ||
| <div> | ||
| <AppHeader /> | ||
|
|
||
| <UMain> | ||
| <slot /> | ||
| </UMain> | ||
| </div> | ||
| </template> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import wasm from 'vite-plugin-wasm' | ||
| import topLevelAwait from 'vite-plugin-top-level-await' | ||
|
|
||
| export default defineNuxtConfig({ | ||
|
|
||
| modules: [ | ||
| '@powersync/nuxt', | ||
| '@nuxt/eslint', | ||
| '@nuxt/ui', | ||
| '@nuxtjs/supabase', | ||
| ], | ||
| ssr: false, | ||
|
|
||
| devtools: { | ||
| enabled: true, | ||
| }, | ||
|
|
||
| css: ['~/assets/css/main.css'], | ||
|
|
||
| runtimeConfig: { | ||
| public: { | ||
| powersyncUrl: process.env.NUXT_PUBLIC_POWERSYNC_URL, | ||
| }, | ||
| }, | ||
|
|
||
| // enable hot reloading when we make changes to our module | ||
| watch: ['../src/*', './**/*'], | ||
|
|
||
| compatibilityDate: '2024-07-05', | ||
|
|
||
| vite: { | ||
| plugins: [topLevelAwait()], | ||
| optimizeDeps: { | ||
| exclude: ['@journeyapps/wa-sqlite', '@powersync/web', '@powersync/common', '@powersync/vue', '@powersync/kysely-driver'], | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to exclude all |
||
| include: [ | ||
| '@powersync/web > js-logger', | ||
| '@supabase/postgrest-js', | ||
| ], | ||
| }, | ||
|
|
||
| worker: { | ||
| format: 'es', | ||
| plugins: () => [wasm(), topLevelAwait()], | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe you should be able to remove the |
||
| }, | ||
| }, | ||
|
|
||
| unocss: { | ||
| autoImport: false, | ||
| }, | ||
|
|
||
| eslint: { | ||
| config: { | ||
| stylistic: true, | ||
| }, | ||
| }, | ||
|
|
||
| powersync: { | ||
| useDiagnostics: true, | ||
| }, | ||
|
|
||
| supabase: { | ||
| url: process.env.NUXT_PUBLIC_SUPABASE_URL, | ||
| key: process.env.NUXT_PUBLIC_SUPABASE_ANON_KEY, | ||
| redirectOptions: { | ||
| login: '/login', | ||
| callback: '/confirm', | ||
| // include: ['/protected'], | ||
| exclude: ['/unprotected', '/public/*'], | ||
| }, | ||
| clientOptions: { | ||
| auth: { | ||
| persistSession: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably add a note to update the password in
db/seed.sqlbefore running?