Skip to content

Commit f720d62

Browse files
authored
Add self-hosted time-based sync demo (#892)
1 parent 2e92c33 commit f720d62

38 files changed

+1217
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Demo applications are located in the [`demos/`](./demos/) directory. Also see ou
7373

7474
- [demos/react-supabase-todolist](./demos/react-supabase-todolist/README.md): A React to-do list example app using the PowerSync Web SDK and a Supabase backend.
7575
- [demos/react-supabase-todolist-tanstackdb](./demos/react-supabase-todolist-tanstackdb/README.md): A React to-do list example app using the PowerSync Web SDK and a Supabase backend + [TanStackDB](https://tanstack.com/db/latest) collections.
76+
- [demos/react-supabase-time-based-sync](./demos/react-supabase-time-based-sync/README.md): A React demo using Sync Streams to subscribe to date-filtered data dynamically, with a Supabase backend.
7677
- [demos/react-multi-client](./demos/react-multi-client/README.md): A React widget that illustrates how data flows from one PowerSync client to another.
7778
- [demos/yjs-react-supabase-text-collab](./demos/yjs-react-supabase-text-collab/README.md): A React real-time text editing collaboration example app powered by [Yjs](https://github.com/yjs/yjs) CRDTs and [Tiptap](https://tiptap.dev/), using the PowerSync Web SDK and a Supabase backend.
7879
- [demos/vue-supabase-todolist](./demos/vue-supabase-todolist/README.md): A Vue to-do list example app using the PowerSync Web SDK and a Supabase backend.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copy this template: `cp .env.local.template .env.local`
2+
# Values below point to local Supabase + local PowerSync.
3+
# The anon key is the well-known default for all local Supabase instances.
4+
VITE_SUPABASE_URL=http://127.0.0.1:54321
5+
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
6+
VITE_POWERSYNC_URL=http://127.0.0.1:8080
7+
8+
# PowerSync Service port
9+
PS_PORT=8080
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2+
3+
# dependencies
4+
node_modules/
5+
6+
# Metrom
7+
.metro-health-check*
8+
9+
# debug
10+
npm-debug.*
11+
12+
# local env files
13+
.env*.local
14+
.env
15+
16+
# typescript
17+
*.tsbuildinfo
18+
19+
# IDE
20+
.vscode
21+
.fleet
22+
.idea
23+
24+
ios/
25+
android/
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# PowerSync + Supabase: Time-Based Sync
2+
3+
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 based on a date. The backend contains a set of issues with `created_at` / `updated_at` as **`TIMESTAMPTZ`** in Postgres. Each selected date creates its own Sync Stream subscription with a `date` parameter. Toggling dates on or off adds or removes stream subscriptions and PowerSync syncs the matching issues. TTL is set to 0 so data is removed when dates are deselected.
4+
5+
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 Streams.
6+
7+
The stream definition lives in `powersync/sync-config.yaml`:
8+
9+
```yaml
10+
streams:
11+
issues_by_date:
12+
query: |
13+
SELECT * FROM issues
14+
WHERE substring(updated_at, 1, 10) = subscription.parameter('date')
15+
```
16+
17+
Postgres `TIMESTAMPTZ` values are handled like text for the first 10 characters (the `YYYY-MM-DD` prefix) in both the stream query and on the client replica.
18+
19+
The client implementation is in `src/app/views/issues/page.tsx`. It builds an array of stream options from the selected dates and passes them directly to `useQuery` via the `streams` option:
20+
21+
```tsx
22+
import { useQuery } from '@powersync/react';
23+
24+
const streams = selectedDates.map((date) => ({
25+
name: 'issues_by_date',
26+
parameters: { date },
27+
ttl: 0
28+
}));
29+
30+
const { data: issues } = useQuery(
31+
'SELECT * FROM issues ORDER BY updated_at DESC',
32+
[],
33+
{ streams }
34+
);
35+
```
36+
37+
`useQuery` manages the stream subscriptions internally — subscribing to new streams and unsubscribing from removed ones as the array changes.
38+
39+
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.
40+
41+
## Prerequisites
42+
43+
- [Docker](https://docs.docker.com/get-docker/) (running)
44+
- [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started)
45+
- [PowerSync CLI](https://docs.powersync.com/tools/cli)
46+
47+
## Local development (recommended)
48+
49+
1. Switch into this demo:
50+
51+
```bash
52+
cd demos/react-supabase-time-based-sync
53+
```
54+
55+
2. Install dependencies:
56+
57+
```bash
58+
pnpm install
59+
```
60+
61+
3. Create env file:
62+
63+
```bash
64+
cp .env.local.template .env.local
65+
```
66+
67+
The template already contains the well-known local Supabase anon key, so no manual changes are needed.
68+
69+
4. Start local Supabase + local PowerSync:
70+
71+
> Ensure the [PowerSync CLI](https://docs.powersync.com/tools/cli) is installed before running the following command.
72+
73+
```bash
74+
pnpm local:up
75+
```
76+
77+
This does three things:
78+
- starts Supabase Docker services
79+
- starts PowerSync using the checked-in `powersync/service.yaml`
80+
- loads sync streams from `powersync/sync-config.yaml`
81+
82+
5. Start the app:
83+
84+
```bash
85+
pnpm dev
86+
```
87+
88+
Open [http://localhost:5173](http://localhost:5173).
89+
90+
## Database setup and seed data
91+
92+
The schema and seed data are in `supabase/migrations/20260312000000_init_issues.sql`.
93+
94+
When Supabase starts for the first time, the migration creates:
95+
96+
- the `issues` table (`created_at` / `updated_at` are `TIMESTAMPTZ`)
97+
- RLS policies for authenticated users (including anonymous sessions)
98+
- realtime publication for `issues`
99+
- sample issues used by the time-based sync filters
100+
101+
Run `supabase db reset` to re-apply migrations from scratch (required if you previously applied this migration when `created_at` / `updated_at` were `TEXT`).
102+
103+
```bash
104+
supabase db reset
105+
```
106+
107+
## Notes
108+
109+
- The app signs in with `signInAnonymously()` automatically in the connector.
110+
- No login/register routes are used in this demo.
111+
- To stop local services:
112+
113+
```bash
114+
pnpm local:down
115+
```
116+
117+
## Learn More
118+
119+
- [PowerSync CLI docs](https://docs.powersync.com/tools/cli)
120+
- [PowerSync Sync Streams](https://docs.powersync.com/sync/sync-streams)
121+
- [Supabase anonymous sign-ins](https://supabase.com/docs/guides/auth/auth-anonymous)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "react-supabase-time-based-sync",
3+
"version": "0.1.0",
4+
"private": true,
5+
"description": "PowerSync React demo for time-based sync using sync streams (edition 3)",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.node.json && vite build",
9+
"preview": "vite preview",
10+
"start": "pnpm build && pnpm preview",
11+
"local:up": "supabase start && powersync docker start",
12+
"local:down": "powersync docker stop && supabase stop"
13+
},
14+
"dependencies": {
15+
"@powersync/react": "^1.10.0",
16+
"@powersync/web": "^1.37.1",
17+
"@emotion/react": "11.11.4",
18+
"@emotion/styled": "11.11.5",
19+
"@journeyapps/wa-sqlite": "^1.5.0",
20+
"@mui/icons-material": "^5.15.12",
21+
"@mui/material": "^5.15.12",
22+
"@supabase/supabase-js": "^2.39.7",
23+
"formik": "^2.4.6",
24+
"react": "^18.2.0",
25+
"react-dom": "^18.2.0",
26+
"react-router-dom": "^6.22.3"
27+
},
28+
"devDependencies": {
29+
"@swc/core": "~1.6.0",
30+
"@types/node": "^20.11.25",
31+
"@types/react": "^18.2.64",
32+
"@types/react-dom": "^18.2.21",
33+
"@vitejs/plugin-react": "^4.2.1",
34+
"typescript": "^5.4.2",
35+
"vite": "^5.1.5"
36+
}
37+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages:
2+
- .
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
type: self-hosted
2+
api_url: http://localhost:8080
3+
api_key: dev-token
4+
plugins:
5+
docker:
6+
project_name: powersync_react-supabase-time-based-sync
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Composed PowerSync Docker stack (generated by powersync docker configure).
2+
# Modules add entries to include and to services.powersync.depends_on.
3+
# Relative paths: . = powersync/docker, .. = powersync.
4+
# Include syntax requires Docker Compose v2.20.3+
5+
6+
include: []
7+
8+
services:
9+
powersync:
10+
restart: unless-stopped
11+
image: journeyapps/powersync-service:latest
12+
command: [ 'start', '-r', 'unified' ]
13+
env_file:
14+
- ../../.env.local
15+
volumes:
16+
- ../service.yaml:/config/service.yaml
17+
- ../sync-config.yaml:/config/sync-config.yaml
18+
environment:
19+
POWERSYNC_CONFIG_PATH: /config/service.yaml
20+
NODE_OPTIONS: --max-old-space-size=1000
21+
healthcheck:
22+
test:
23+
- 'CMD'
24+
- 'node'
25+
- '-e'
26+
- "fetch('http://localhost:${PS_PORT:-8080}/probes/liveness').then(r =>
27+
r.ok ? process.exit(0) : process.exit(1)).catch(() =>
28+
process.exit(1))"
29+
interval: 5s
30+
timeout: 1s
31+
retries: 15
32+
ports:
33+
- '${PS_PORT:-8080}:${PS_PORT:-8080}'
34+
depends_on: {}
35+
name: powersync_react-supabase-time-based-sync
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# yaml-language-server: $schema=https://unpkg.com/@powersync/service-schema@latest/json-schema/powersync-config.json
2+
_type: self-hosted
3+
4+
replication:
5+
connections:
6+
- type: postgresql
7+
uri: postgresql://postgres:postgres@host.docker.internal:54322/postgres
8+
sslmode: disable
9+
10+
storage:
11+
type: postgresql
12+
uri: postgresql://postgres:postgres@host.docker.internal:54322/postgres
13+
sslmode: disable
14+
15+
sync_config:
16+
path: ./sync-config.yaml
17+
18+
port: 8080
19+
20+
client_auth:
21+
jwks_uri: http://host.docker.internal:54321/auth/v1/.well-known/jwks.json
22+
audience:
23+
- authenticated
24+
25+
telemetry:
26+
prometheus_port: 9090
27+
disable_telemetry_sharing: true
28+
29+
api:
30+
tokens:
31+
- dev-token
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
config:
2+
edition: 3
3+
4+
streams:
5+
issues_by_date:
6+
query: |
7+
SELECT * FROM issues
8+
WHERE substring(updated_at, 1, 10) = subscription.parameter('date')

0 commit comments

Comments
 (0)