Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules
lib
dist
.nuxt
.output
*.tsbuildinfo
.vscode
.DS_STORE
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20.9.0
v20
23 changes: 23 additions & 0 deletions demos/nuxt-supabase-todolist/.env.template
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
1 change: 1 addition & 0 deletions demos/nuxt-supabase-todolist/.nuxtrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
imports.autoImport=true
95 changes: 95 additions & 0 deletions demos/nuxt-supabase-todolist/README.md
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.
- **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

- [PowerSync Documentation](https://docs.powersync.com/)
- [Supabase Documentation](https://supabase.com/docs)
- [Nuxt Documentation](https://nuxt.com/)

20 changes: 20 additions & 0 deletions demos/nuxt-supabase-todolist/app.config.ts
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',
},
},
},
})
58 changes: 58 additions & 0 deletions demos/nuxt-supabase-todolist/app.vue
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>
8 changes: 8 additions & 0 deletions demos/nuxt-supabase-todolist/assets/css/main.css
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%;
}
43 changes: 43 additions & 0 deletions demos/nuxt-supabase-todolist/components/AppHeader.vue
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"
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>
28 changes: 28 additions & 0 deletions demos/nuxt-supabase-todolist/db/seed.sql
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;
9 changes: 9 additions & 0 deletions demos/nuxt-supabase-todolist/eslint.config.mjs
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',
},
})
9 changes: 9 additions & 0 deletions demos/nuxt-supabase-todolist/layouts/default.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<template>
<div>
<AppHeader />

<UMain>
<slot />
</UMain>
</div>
</template>
76 changes: 76 additions & 0 deletions demos/nuxt-supabase-todolist/nuxt.config.ts
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'],
include: [
'@powersync/web > js-logger',
'@supabase/postgrest-js',
],
},

worker: {
format: 'es',
plugins: () => [wasm(), topLevelAwait()],
},
},

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,
},
},
},
})
Loading
Loading