-
Notifications
You must be signed in to change notification settings - Fork 64
Add self-hosted time-based sync demo #892
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
Open
bean1352
wants to merge
10
commits into
main
Choose a base branch
from
feat/add-time-based-sync-demo
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
20f6e89
Add React Supabase time-based sync demo with initial setup and compon…
bean1352 6e5920f
Update Supabase demo configuration and improve README instructions
bean1352 9773b43
Updated IssuesPage to implement a new DateIssuesSection component for…
bean1352 946317e
Merge branch 'main' into feat/add-time-based-sync-demo
bean1352 6146eca
Use json_each() for single-subscription date filtering
bean1352 ba4e205
Merge branch 'main' into feat/add-time-based-sync-demo
bean1352 7bd3d18
Don't ignore .env in ./powersync/docker
bean1352 f9d835f
Fixed flicker when subscribing/unsubscribing to streams and updated t…
bean1352 472365d
Merge branch 'main' into feat/add-time-based-sync-demo
bean1352 c9ec9b8
Store dates as TIMESTAMPZ in Supabase
bean1352 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Copy this template: `cp .env.local.template .env.local` | ||
| # Values below point to local Supabase + local PowerSync. | ||
| # The anon key is the well-known default for all local Supabase instances. | ||
| VITE_SUPABASE_URL=http://127.0.0.1:54321 | ||
| VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 | ||
| VITE_POWERSYNC_URL=http://127.0.0.1:8080 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files | ||
|
|
||
| # dependencies | ||
| node_modules/ | ||
|
|
||
| # Metrom | ||
| .metro-health-check* | ||
|
|
||
| # debug | ||
| npm-debug.* | ||
|
|
||
| # local env files | ||
| .env*.local | ||
| .env | ||
| # don't ignore env file in powersync/docker directory | ||
| !powersync/docker/.env | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
|
|
||
| # IDE | ||
| .vscode | ||
| .fleet | ||
| .idea | ||
|
|
||
| ios/ | ||
| android/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # PowerSync + Supabase: Time-Based Sync (Local-First) | ||
|
|
||
| This demo shows how to use [PowerSync Sync Streams](https://docs.powersync.com/sync/sync-streams) to dynamically control which data is synced to the client. The backend contains a set of issues with `created_at` / `updated_at` as **`TIMESTAMPTZ`** in Postgres. The client passes the selected **UTC calendar dates** (`YYYY-MM-DD`) as a JSON array to a single sync stream subscription. Toggling dates on or off updates the array and PowerSync syncs the matching issues. TTL is set to 0 so data is removed immediately when dates are deselected. | ||
|
|
||
| This lets you model patterns like "sync the last N days of data" or "sync only the time ranges the user cares about" without re-deploying sync rules. | ||
|
|
||
| The stream definition lives in `powersync/sync-config.yaml` and uses `json_each()` to expand the array parameter: | ||
|
|
||
| ```yaml | ||
| streams: | ||
| issues_by_date: | ||
| query: | | ||
| SELECT * FROM issues | ||
| WHERE substring(updated_at, 1, 10) IN (SELECT value FROM json_each(subscription.parameter('dates'))) | ||
| ``` | ||
|
|
||
| Postgres `TIMESTAMPTZ` values are handled like text for the first 10 characters (the `YYYY-MM-DD` prefix) in both the sync stream query and on the client replica. | ||
|
|
||
| The client implementation is in `src/app/views/issues/page.tsx`. It: | ||
|
|
||
| 1. **Filters the local query** with the same predicate as the stream (`substring(updated_at, 1, 10)` plus bound `?` placeholders), or `WHERE 1 = 0` when no dates are selected. | ||
| 2. **Subscribes via `useSyncStream` in a small child** that only mounts when at least one date is selected (`ttl: 0` matches immediate eviction when nothing is selected). | ||
| 3. **Does not pass `streams` into `useQuery`** — doing so resets internal “stream synced” state on every parameter change and briefly clears `data`, which flickers the list when toggling chips quickly. | ||
|
|
||
| ```tsx | ||
| import { useQuery, useSyncStream } from '@powersync/react'; | ||
|
|
||
| function IssuesByDateStreamSubscription({ datesParam }: { datesParam: string }) { | ||
| useSyncStream({ name: 'issues_by_date', parameters: { dates: datesParam }, ttl: 0 }); | ||
| return null; | ||
| } | ||
|
|
||
| // Inside your page component: | ||
| const datesParam = React.useMemo(() => JSON.stringify(selectedDates), [selectedDates]); | ||
|
|
||
| const { issuesSql, issuesParams } = React.useMemo(() => { | ||
| if (selectedDates.length === 0) { | ||
| return { | ||
| issuesSql: `SELECT * FROM issues WHERE 1 = 0 ORDER BY created_at DESC`, | ||
| issuesParams: [] as string[] | ||
| }; | ||
| } | ||
| const placeholders = selectedDates.map(() => '?').join(', '); | ||
| return { | ||
| issuesSql: `SELECT * FROM issues WHERE substring(updated_at, 1, 10) IN (${placeholders}) ORDER BY created_at DESC`, | ||
| issuesParams: selectedDates | ||
| }; | ||
| }, [selectedDates]); | ||
|
|
||
| const { data: issues } = useQuery(issuesSql, issuesParams); | ||
|
|
||
| return ( | ||
| <> | ||
| {selectedDates.length > 0 ? ( | ||
| <IssuesByDateStreamSubscription datesParam={datesParam} /> | ||
| ) : null} | ||
| {/* …render chips and list from `issues`… */} | ||
| </> | ||
| ); | ||
| ``` | ||
|
|
||
| In the repo, those `SELECT * FROM issues` fragments use `` `${ISSUES_TABLE}` `` from `src/library/powersync/AppSchema.ts` (the table name is `'issues'`), and the hook is `useQuery<IssueRecord>(issuesSql, issuesParams)`. | ||
|
|
||
| The demo runs against local Supabase (`supabase start`) and self-hosted PowerSync (via the PowerSync CLI). It uses anonymous Supabase auth — there is no login or registration flow. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - [Docker](https://docs.docker.com/get-docker/) (running) | ||
| - [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started) | ||
| - [PowerSync CLI](https://docs.powersync.com/tools/cli) | ||
|
|
||
| ## Local development (recommended) | ||
|
|
||
| 1. Switch into this demo: | ||
|
|
||
| ```bash | ||
| cd demos/react-supabase-time-based-sync | ||
| ``` | ||
|
|
||
| 2. Install dependencies: | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| 3. Create env file: | ||
|
|
||
| ```bash | ||
| cp .env.local.template .env.local | ||
| ``` | ||
|
|
||
| The template already contains the well-known local Supabase anon key, so no manual changes are needed. | ||
|
|
||
| 4. Start local Supabase + local PowerSync: | ||
|
|
||
| ```bash | ||
| pnpm local:up | ||
| ``` | ||
|
|
||
| This does three things: | ||
| - starts Supabase Docker services | ||
| - starts PowerSync using the checked-in `powersync/service.yaml` | ||
| - loads sync streams from `powersync/sync-config.yaml` | ||
|
|
||
| 5. Start the app: | ||
|
|
||
| ```bash | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:5173](http://localhost:5173). | ||
|
|
||
| ## Database setup and seed data | ||
|
|
||
| The schema and seed data are in `supabase/migrations/20260312000000_init_issues.sql`. | ||
|
|
||
| When Supabase starts for the first time, the migration creates: | ||
|
|
||
| - the `issues` table (`created_at` / `updated_at` are `TIMESTAMPTZ`) | ||
| - RLS policies for authenticated users (including anonymous sessions) | ||
| - realtime publication for `issues` | ||
| - sample issues used by the time-based sync filters | ||
|
|
||
| Run `supabase db reset` to re-apply migrations from scratch (required if you previously applied this migration when `created_at` / `updated_at` were `TEXT`). | ||
|
|
||
| ```bash | ||
| supabase db reset | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| - The app signs in with `signInAnonymously()` automatically in the connector. | ||
| - No login/register routes are used in this demo. | ||
| - To stop local services: | ||
|
|
||
| ```bash | ||
| pnpm local:down | ||
| ``` | ||
|
|
||
| ## Learn More | ||
|
|
||
| - [PowerSync CLI docs](https://docs.powersync.com/tools/cli) | ||
| - [PowerSync Sync Streams](https://docs.powersync.com/sync/sync-streams) | ||
| - [Supabase anonymous sign-ins](https://supabase.com/docs/guides/auth/auth-anonymous) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| { | ||
| "name": "react-supabase-time-based-sync", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "description": "PowerSync React demo for time-based sync using sync streams (edition 3)", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.node.json && vite build", | ||
| "preview": "vite preview", | ||
| "start": "pnpm build && pnpm preview", | ||
| "local:up": "supabase start && powersync docker start", | ||
| "local:down": "powersync docker stop && supabase stop" | ||
| }, | ||
| "dependencies": { | ||
| "@powersync/react": "^1.9.0", | ||
| "@powersync/web": "^1.34.0", | ||
| "@emotion/react": "11.11.4", | ||
| "@emotion/styled": "11.11.5", | ||
| "@journeyapps/wa-sqlite": "^1.5.0", | ||
| "@mui/icons-material": "^5.15.12", | ||
| "@mui/material": "^5.15.12", | ||
| "@supabase/supabase-js": "^2.39.7", | ||
| "formik": "^2.4.6", | ||
| "react": "^18.2.0", | ||
| "react-dom": "^18.2.0", | ||
| "react-router-dom": "^6.22.3" | ||
| }, | ||
| "devDependencies": { | ||
| "@swc/core": "~1.6.0", | ||
| "@types/node": "^20.11.25", | ||
| "@types/react": "^18.2.64", | ||
| "@types/react-dom": "^18.2.21", | ||
| "@vitejs/plugin-react": "^4.2.1", | ||
| "typescript": "^5.4.2", | ||
| "vite": "^5.1.5" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| packages: | ||
| - . |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| type: self-hosted | ||
| api_url: http://localhost:8080 | ||
| api_key: dev-token | ||
| plugins: | ||
| docker: | ||
| project_name: powersync_react-supabase-time-based-sync |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| PS_PORT=8080 | ||
| PS_DATA_SOURCE_URI=postgresql://postgres:postgres@supabase_db_react-supabase-time-based-sync:5432/postgres | ||
| PS_STORAGE_SOURCE_URI=postgresql://postgres:postgres@supabase_db_react-supabase-time-based-sync:5432/postgres |
35 changes: 35 additions & 0 deletions
35
demos/react-supabase-time-based-sync/powersync/docker/docker-compose.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # Composed PowerSync Docker stack (generated by powersync docker configure). | ||
| # Modules add entries to include and to services.powersync.depends_on. | ||
| # Relative paths: . = powersync/docker, .. = powersync. | ||
| # Include syntax requires Docker Compose v2.20.3+ | ||
|
|
||
| include: [] | ||
|
|
||
| services: | ||
| powersync: | ||
| restart: unless-stopped | ||
| image: journeyapps/powersync-service:latest | ||
| command: [ 'start', '-r', 'unified' ] | ||
| env_file: | ||
| - .env | ||
| volumes: | ||
| - ../service.yaml:/config/service.yaml | ||
| - ../sync-config.yaml:/config/sync-config.yaml | ||
| environment: | ||
| POWERSYNC_CONFIG_PATH: /config/service.yaml | ||
| NODE_OPTIONS: --max-old-space-size=1000 | ||
| healthcheck: | ||
| test: | ||
| - 'CMD' | ||
| - 'node' | ||
| - '-e' | ||
| - "fetch('http://localhost:${PS_PORT:-8080}/probes/liveness').then(r => | ||
| r.ok ? process.exit(0) : process.exit(1)).catch(() => | ||
| process.exit(1))" | ||
| interval: 5s | ||
| timeout: 1s | ||
| retries: 15 | ||
| ports: | ||
| - '${PS_PORT:-8080}:${PS_PORT:-8080}' | ||
| depends_on: {} | ||
| name: powersync_react-supabase-time-based-sync | ||
31 changes: 31 additions & 0 deletions
31
demos/react-supabase-time-based-sync/powersync/service.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # yaml-language-server: $schema=https://unpkg.com/@powersync/service-schema@latest/json-schema/powersync-config.json | ||
| _type: self-hosted | ||
|
|
||
| replication: | ||
| connections: | ||
| - type: postgresql | ||
| uri: postgresql://postgres:postgres@host.docker.internal:54322/postgres | ||
| sslmode: disable | ||
|
|
||
| storage: | ||
| type: postgresql | ||
| uri: postgresql://postgres:postgres@host.docker.internal:54322/postgres | ||
| sslmode: disable | ||
|
|
||
| sync_config: | ||
| path: ./sync-config.yaml | ||
|
|
||
| port: 8080 | ||
|
|
||
| client_auth: | ||
| jwks_uri: http://host.docker.internal:54321/auth/v1/.well-known/jwks.json | ||
| audience: | ||
| - authenticated | ||
|
|
||
| telemetry: | ||
| prometheus_port: 9090 | ||
| disable_telemetry_sharing: true | ||
|
|
||
| api: | ||
| tokens: | ||
| - dev-token |
8 changes: 8 additions & 0 deletions
8
demos/react-supabase-time-based-sync/powersync/sync-config.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| config: | ||
| edition: 3 | ||
|
|
||
| streams: | ||
| issues_by_date: | ||
| query: | | ||
| SELECT * FROM issues | ||
| WHERE substring(updated_at, 1, 10) IN (SELECT value FROM json_each(subscription.parameter('dates'))) |
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| :root { | ||
| --foreground-rgb: 255, 255, 255; | ||
| --background-start-rgb: 0, 0, 0; | ||
| --background-end-rgb: 0, 0, 0; | ||
| } | ||
|
|
||
| body { | ||
| color: rgb(var(--foreground-rgb)); | ||
| min-height: 100vh; | ||
| margin: 0; | ||
| background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb)); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { createRoot } from 'react-dom/client'; | ||
| import { RouterProvider } from 'react-router-dom'; | ||
| import { SystemProvider } from '@/components/providers/SystemProvider'; | ||
| import { ThemeProviderContainer } from '@/components/providers/ThemeProviderContainer'; | ||
| import { router } from '@/app/router'; | ||
|
|
||
| const root = createRoot(document.getElementById('app')!); | ||
| root.render(<App />); | ||
|
|
||
| export function App() { | ||
| return ( | ||
| <ThemeProviderContainer> | ||
| <SystemProvider> | ||
| <RouterProvider router={router} /> | ||
| </SystemProvider> | ||
| </ThemeProviderContainer> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { Outlet, createBrowserRouter, Navigate } from 'react-router-dom'; | ||
| import IssuesPage from '@/app/views/issues/page'; | ||
| import ViewsLayout from '@/app/views/layout'; | ||
|
|
||
| export const ISSUES_ROUTE = '/views/issues'; | ||
| export const DEFAULT_ENTRY_ROUTE = ISSUES_ROUTE; | ||
|
|
||
| export const router = createBrowserRouter([ | ||
| { | ||
| path: '/', | ||
| element: <Navigate to={DEFAULT_ENTRY_ROUTE} replace /> | ||
| }, | ||
| { | ||
| element: ( | ||
| <ViewsLayout> | ||
| <Outlet /> | ||
| </ViewsLayout> | ||
| ), | ||
| children: [ | ||
| { | ||
| path: ISSUES_ROUTE, | ||
| element: <IssuesPage /> | ||
| } | ||
| ] | ||
| } | ||
| ]); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.