This repo includes a debug-only PowerSync sandbox that validates the full local flow:
- Papyrus authentication
- Papyrus-issued PowerSync JWTs
- self-hosted PowerSync service
- client writes through the PowerSync upload queue
- replication back into another browser client
The sandbox uses a dedicated demo table, not the real books domain.
- source table:
powersync_demo_items - PowerSync debug page:
/__dev/powersync-sandbox - source snapshot API:
/__dev/powersync-demo/items - upload endpoint used by the PowerSync queue:
/__dev/powersync-demo/upload
Make sure .env includes the PowerSync values from .env.example, especially:
POWERSYNC_JWT_PRIVATE_KEY_FILE=.local/powersync/private.pem
POWERSYNC_JWT_PUBLIC_KEY_FILE=.local/powersync/public.pem
POWERSYNC_JWT_KEY_ID=papyrus-powersync-dev
POWERSYNC_JWT_AUDIENCE=powersync-dev
POWERSYNC_SERVICE_URL=http://localhost:8081
POWERSYNC_JWKS_URI=http://host.docker.internal:8080/v1/auth/jwks
POWERSYNC_SOURCE_ROLE=powersync_role
POWERSYNC_SOURCE_PASSWORD=powersync_dev_password
POWERSYNC_STORAGE_DB=powersync_storage
POWERSYNC_STORAGE_USER=powersync_storage_user
POWERSYNC_STORAGE_PASSWORD=powersync_storage_passwordIf the API runs inside Docker instead of on the host, point POWERSYNC_JWKS_URI at the container-to-container API URL instead of host.docker.internal.
- Generate local PowerSync signing keys:
./scripts/generate_dev_powersync_keys.sh- Start the local dependencies:
docker compose up -d database mailpit powersync-storage- Apply migrations:
uv run alembic upgrade head- Create the PowerSync replication role and publication:
./scripts/setup_local_powersync.sh- Start the backend on the host:
uv run uvicorn papyrus.main:app --reload --port 8080- Start the sandbox asset dev server for live TS/SCSS reload:
npm --prefix frontend/dev-pages install
npm --prefix frontend/dev-pages run dev- Start the PowerSync service:
docker compose up -d powersync- API index:
http://localhost:8080/ - PowerSync sandbox:
http://localhost:8080/__dev/powersync-sandbox - Client one:
http://localhost:8080/__dev/powersync-sandbox?client=one - Client two:
http://localhost:8080/__dev/powersync-sandbox?client=two - Swagger UI:
http://localhost:8080/docs - Mailpit:
http://localhost:8025 - PowerSync service:
http://localhost:8081
- Open
client=oneandclient=twoin separate tabs. - Register or log in as the same user in both tabs.
- In one tab, connect PowerSync.
- In the other tab, connect PowerSync.
- Create a demo item in either tab.
- Confirm the item appears in:
- the local synced list in the creating tab
- the server source snapshot
- the local synced list in the second tab without a manual refresh
- Update the item from the second tab.
- Confirm the first tab updates automatically.
- Delete the item from either tab.
- Confirm it disappears from both tabs and the source snapshot.
Repeat the same flow once after signing in through Google OAuth to confirm that Papyrus-issued PowerSync credentials work for provider-authenticated users too.
This sandbox is working correctly when all of the following are true:
- the page can authenticate through Papyrus
POST /v1/auth/powersync-tokenreturns a valid PowerSync JWT- local item writes are visible in the source snapshot
- the second client receives replicated changes automatically
- updates and deletes also replicate back into the other client
If you change the PowerSync config, replication setup, or local browser database and the sandbox gets into a bad state:
- Stop the stack:
docker compose down- Remove local Docker volumes if needed:
docker compose down -v- Clear the browser data for the sandbox page, or use a different
?client=name. - Re-run:
docker compose up -d database mailpit powersync-storageuv run alembic upgrade head./scripts/setup_local_powersync.shuv run uvicorn papyrus.main:app --reload --port 8080npm --prefix frontend/dev-pages run devdocker compose up -d powersync
- The source database table is managed with Alembic.
- The PowerSync publication and replication role are not managed with Alembic; they are initialized by
scripts/setup_local_powersync.sh. - The sandbox is debug-only and should not be exposed in production mode.
- If you prefer built assets over the Vite dev server, run
npm --prefix frontend/dev-pages run buildand setDEV_PAGES_USE_VITE=false.