From 1ee4949300ba8dee38d7e847d7d2017ca5dd2134 Mon Sep 17 00:00:00 2001 From: bean1352 Date: Wed, 18 Mar 2026 14:41:24 +0200 Subject: [PATCH 1/9] Update PowerSync skill: CLI-first flow, backends, env, refs --- CONTRIBUTING.md | 8 +- skills/powersync/AGENTS.md | 90 +++++++++---------- skills/powersync/CLAUDE.md | 2 +- skills/powersync/SKILL.md | 89 +++++++++--------- .../references/onboarding-supabase-web.md | 16 ++-- .../references/sdks/powersync-js-react.md | 32 +++++++ 6 files changed, 131 insertions(+), 106 deletions(-) mode change 120000 => 100644 skills/powersync/CLAUDE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3b727b..4a04488 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,9 +8,9 @@ This repo contains a single skill (`skills/powersync/`) following the [Agent Ski ``` skills/powersync/ -├── CLAUDE.md # Entry point for Claude Code -├── AGENTS.md # Entry point for other agents (Cursor, Codex, etc.) -├── SKILL.md # Entry point for skills.sh +├── CLAUDE.md # Redirect to AGENTS.md (for Claude Code) +├── AGENTS.md # Primary entry point for all agents (Cursor, Codex, Claude, etc.) +├── SKILL.md # Entry point for skills.sh (includes YAML frontmatter + same content as AGENTS.md) └── references/ ├── sync-config.md ├── powersync-service.md @@ -89,7 +89,7 @@ If you add an example that touches these patterns, make sure it reflects these r ## Submitting a Pull Request 1. Fork the repo and create a branch from `main`. -2. Make your changes. If adding a new reference file, also update the routing table in `CLAUDE.md` and `SKILL.md`. +2. Make your changes. If adding a new reference file, update the routing table in `AGENTS.md` and `SKILL.md`. (`CLAUDE.md` is a redirect and does not need changes.) 3. Test your changes by installing the skill locally and asking an agent a question that exercises the updated content: ``` npx skills add diff --git a/skills/powersync/AGENTS.md b/skills/powersync/AGENTS.md index ba09f42..2495fe0 100644 --- a/skills/powersync/AGENTS.md +++ b/skills/powersync/AGENTS.md @@ -24,59 +24,53 @@ Full CLI reference: `references/powersync-cli.md` — **always load this file** When the task is to add PowerSync to an app, follow this sequence in order: 1. Identify the platform: **Cloud** or **self-hosted**. -2. Identify the backend: **Supabase** or another database. +2. **Identify the backend.** If the user has not specified a backend, **ask them** which database/backend they want to use (e.g. Supabase, custom Postgres, MongoDB, MySQL, MSSQL). Do not assume Supabase. The choice determines which references to load: + - **Supabase** → load `references/onboarding-supabase-web.md` + `references/supabase-auth.md` + - **Any other backend** → load `references/custom-backend.md` — the agent must create a backend API with an `uploadData`, `token` endpoint and a JWT auth provider. Do not skip this. 3. If the backend is Supabase and it is unclear whether the user means **online (Supabase Cloud)** or **locally hosted** (e.g. `supabase start`), **ask the user** before choosing connection strings, auth config, or references. 4. Collect required inputs before coding. -5. Generate sync config and any required source database setup (e.g. Supabase publication SQL, Postgres publication, MongoDB replica set). -6. **Create/link the instance and deploy config before writing app code.** Use the CLI — do not create config files manually. For Cloud: `powersync init cloud` → edit config → `powersync link cloud --create` → `powersync deploy`. For self-hosted: `powersync init self-hosted` → `powersync docker configure` → `powersync docker start`. For source database setup the agent cannot run (e.g. Supabase publication SQL), present the exact SQL and ask the user to confirm it is done. The app will not sync without deployed config. -7. Only after backend readiness is confirmed, implement app-side PowerSync integration. +5. **Always load `references/sync-config.md`** and generate sync config. Also set up any required source database configuration (e.g. Supabase publication SQL, Postgres publication, MongoDB replica set). Sync config is mandatory for every PowerSync project — without it, nothing syncs. +6. **Persist all credentials and connection details to `.env` immediately.** When a CLI or dashboard provides database credentials (host, port, database name, username, password, connection URI), write them to the project's `.env` file right away — before deploying config or writing app code. Both `service.yaml` (via `!env` tags) and app code (e.g. `fetchCredentials`) depend on these values. If they are not in `.env`, the PowerSync config will deploy with broken connection details and the app will not connect. Include at minimum: `POWERSYNC_URL`, the Postgres connection URI (e.g. `PS_DATABASE_URI`), and any backend-specific keys. +7. **Create/link the instance and deploy config before writing app code.** Use the CLI — do not create config files manually. For Cloud: `powersync init cloud` → edit config → `powersync link cloud --create` → `powersync deploy`. For self-hosted: `powersync init self-hosted` → `powersync docker configure` → `powersync docker start`. For source database setup the agent cannot run (e.g. Supabase publication SQL), present the exact SQL and ask the user to confirm it is done. The app will not sync without deployed config. +8. Only after backend readiness is confirmed, implement app-side PowerSync integration. Do not start client-side debugging while the PowerSync service is still unconfigured. If the UI is stuck on `Syncing...`, the default diagnosis is incomplete backend setup, not a frontend bug. ## Critical Footguns -Apply these rules without exception: +These apply to all paths. Domain-specific pitfalls are documented in the relevant reference files — only load those when working on that domain. -- `powersync/service.yaml` uses `replication.connections`, not a top-level `connections` key. -- `powersync/sync-config.yaml` must begin with: - ```yaml - config: - edition: 3 - ``` -- `powersync pull instance` silently overwrites local `service.yaml` and `sync-config.yaml`. -- For existing Cloud instances, pull config before manual edits. Never pull after editing unless you have backed up the local files. -- The self-hosted Docker image listens on port **8080**, not 80. Use `-p 8080:8080` in port mapping. -- The Docker image does **not** accept a `-s` flag for sync config. Use the `POWERSYNC_SYNC_CONFIG_B64` environment variable or the `-sync64` flag. -- For local Postgres / local Supabase, set `sslmode: disable` as a YAML key on the connection — the `sslmode=disable` URI query string is ignored by pgwire. -- `PowerSyncBackendConnector`, `PowerSyncCredentials`, and `AbstractPowerSyncDatabase` are **type-only exports** in the JS SDK. Use `import type` — a regular import causes runtime errors in Vite/bundlers. +- After any CLI operation that provisions or links a service (Supabase, PowerSync, or any backend), immediately write the resulting credentials and URLs to the project `.env` file. Do not defer this — downstream config and app code read from `.env` and will break silently if values are missing. +- `powersync pull instance` silently overwrites local `service.yaml` and `sync-config.yaml`. Always back up before pulling. -## Default Benchmark Path - -For a React web app using Supabase auth and PowerSync Cloud, load these files in this order: - -1. `references/onboarding-supabase-web.md` -2. `references/supabase-auth.md` -3. `references/powersync-cli.md` -4. `references/powersync-service.md` -5. `references/sync-config.md` -6. `references/sdks/powersync-js.md` -7. `references/sdks/powersync-js-react.md` - -Use the onboarding recipe as the primary workflow. Use the other references to fill in details, not to invent a different sequence. +Additional footguns are in their reference files — do not load these unless working in that area: +- **Config/CLI:** `references/powersync-cli.md`, `references/powersync-service.md`, `references/sync-config.md` +- **JS/TS SDK:** `references/sdks/powersync-js.md` (type-only imports, connect() semantics, transaction.complete()) +- **React:** `references/sdks/powersync-js-react.md` (Strict Mode, Suspense, Next.js) +- **Supabase:** `references/onboarding-supabase-web.md` (CLI limitations, publication SQL) ## Required Inputs Before Coding Collect the minimum required information for the chosen path before changing app code. -### Cloud + Supabase +### All paths +- Which backend/database (do not assume Supabase — ask if not specified) - Whether the PowerSync instance already exists -- **Whether Supabase is online (hosted at supabase.com) or locally hosted** (e.g. `supabase start`) — if you cannot infer this from the project or env, **prompt the user** - PowerSync instance URL, if an instance already exists - Project ID and instance ID, if using CLI with an existing instance -- Supabase Postgres connection string, if PowerSync still needs the source DB connection +- Source database connection string, if PowerSync still needs the source DB connection + +### Additional for Supabase + +- **Whether Supabase is online (hosted at supabase.com) or locally hosted** (e.g. `supabase start`) — if you cannot infer this from the project or env, **prompt the user** - Whether Supabase JWT signing uses new signing keys or legacy JWT secret, if not obvious from the setup +### Additional for custom backends + +- How the user wants to handle auth (custom JWT, third-party auth provider) +- Whether they have an existing backend API or need to create one (load `references/custom-backend.md`) + Only ask for secrets when you are at the step that actually needs them. ## Cloud Readiness Gate @@ -89,10 +83,11 @@ Do not proceed to app-side code until all items below are verified: - Client auth is configured - Instance URL is available for `fetchCredentials()` - Source database replication/publication setup is complete +- All credentials and URLs are persisted in `.env` (e.g. `POWERSYNC_URL`, `PS_DATABASE_URI`, and any backend-specific keys) If any item is missing, finish the service setup first. -Use the CLI to verify and complete any missing items. For steps the agent cannot perform (e.g. running SQL in Supabase SQL Editor), present the exact commands and ask the user to confirm completion before writing app code. +Use the CLI to verify and complete any missing items. For steps the agent cannot perform (e.g. running SQL in the database), present the exact commands and ask the user to confirm completion before writing app code. ## First Response for `Syncing...` @@ -102,7 +97,7 @@ When the app shows `Syncing...`, check these in order before asking for browser 2. Confirm the PowerSync service has a valid source DB connection. 3. Confirm sync config is deployed and uses `config: edition: 3`. 4. Confirm client auth is configured correctly. -5. Confirm the Supabase publication exists for the synced tables. +5. Confirm the source database publication/replication is set up for the synced tables. 6. Only then inspect frontend connector or SDK state. Before requesting console logs, ask the user to confirm: @@ -111,7 +106,7 @@ Before requesting console logs, ask the user to confirm: - whether database connection is configured - whether sync config was deployed - whether client auth was enabled -- whether the Supabase SQL was run +- whether the source database publication/replication SQL was run ## Setup Paths @@ -187,18 +182,17 @@ Key rule: **client writes never go through PowerSync**. They go from the app's u ## What to Load for Your Task -| Task | Load these files | -|------|-----------------| -| React web app + Supabase + Cloud onboarding | `references/onboarding-supabase-web.md` + `references/supabase-auth.md` + `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` + `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` | -| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` + SDK files for your platform | -| Setting up PowerSync with Supabase | `references/onboarding-supabase-web.md` + `references/supabase-auth.md` | -| Debugging sync / connection issues | `references/powersync-debug.md` | -| Writing or migrating sync config | `references/sync-config.md` | -| Configuring the service / self-hosting | `references/powersync-service.md` + `references/powersync-cli.md` | -| Using the PowerSync CLI | `references/powersync-cli.md` + `references/sync-config.md` | -| Handling file uploads / attachments | `references/attachments.md` | -| Custom backend (non-Supabase) | `references/custom-backend.md` + `references/powersync-service.md` + `references/sync-config.md` + SDK files for your platform | -| Understanding the overall architecture | This file is sufficient; see `references/powersync-overview.md` for deep links | +| Task | Start with | Load on demand | +|------|-----------|----------------| +| React web + Supabase + Cloud | `references/onboarding-supabase-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/supabase-auth.md`, `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` (when writing app code) | +| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` | SDK files for your platform (when writing app code) | +| PowerSync + Supabase | `references/onboarding-supabase-web.md` + `references/sync-config.md` | `references/supabase-auth.md` (when configuring auth) | +| Debugging sync issues | `references/powersync-debug.md` | — | +| Writing sync config | `references/sync-config.md` | — | +| Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` + `references/sync-config.md` | — | +| Attachments | `references/attachments.md` | — | +| Custom backend (non-Supabase) | `references/custom-backend.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/powersync-service.md`, SDK files (when writing app code) | +| Architecture overview | This file is sufficient | `references/powersync-overview.md` for deep links | ## SDK Reference Files diff --git a/skills/powersync/CLAUDE.md b/skills/powersync/CLAUDE.md deleted file mode 120000 index 47dc3e3..0000000 --- a/skills/powersync/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/skills/powersync/CLAUDE.md b/skills/powersync/CLAUDE.md new file mode 100644 index 0000000..bf038a3 --- /dev/null +++ b/skills/powersync/CLAUDE.md @@ -0,0 +1 @@ +See [AGENTS.md](AGENTS.md) for guidance. diff --git a/skills/powersync/SKILL.md b/skills/powersync/SKILL.md index 56bbaac..5bfedd2 100644 --- a/skills/powersync/SKILL.md +++ b/skills/powersync/SKILL.md @@ -36,58 +36,53 @@ Full CLI reference: `references/powersync-cli.md` — **always load this file** When the task is to add PowerSync to an app, follow this sequence in order: 1. Identify the platform: **Cloud** or **self-hosted**. -2. Identify the backend: **Supabase** or another database. +2. **Identify the backend.** If the user has not specified a backend, **ask them** which database/backend they want to use (e.g. Supabase, custom Postgres, MongoDB, MySQL). Do not assume Supabase. The choice determines which references to load: + - **Supabase** → load `references/onboarding-supabase-web.md` + `references/supabase-auth.md` + - **Any other backend** → load `references/custom-backend.md` — the agent must create a backend API with an `uploadData` endpoint and a JWT auth provider. Do not skip this. 3. If the backend is Supabase and it is unclear whether the user means **online (Supabase Cloud)** or **locally hosted** (e.g. `supabase start`), **ask the user** before choosing connection strings, auth config, or references. 4. Collect required inputs before coding. -5. Generate sync config and any required source database setup (e.g. Supabase publication SQL, Postgres publication, MongoDB replica set). -6. **Create/link the instance and deploy config before writing app code.** Use the CLI — do not create config files manually. For Cloud: `powersync init cloud` → edit config → `powersync link cloud --create` → `powersync deploy`. For self-hosted: `powersync init self-hosted` → `powersync docker configure` → `powersync docker start`. For source database setup the agent cannot run (e.g. Supabase publication SQL), present the exact SQL and ask the user to confirm it is done. The app will not sync without deployed config. -7. Only after backend readiness is confirmed, implement app-side PowerSync integration. +5. **Always load `references/sync-config.md`** and generate sync config. Also set up any required source database configuration (e.g. Supabase publication SQL, Postgres publication, MongoDB replica set). Sync config is mandatory for every PowerSync project — without it, nothing syncs. +6. **Persist all credentials and connection details to `.env` immediately.** When a CLI or dashboard provides database credentials (host, port, database name, username, password, connection URI), write them to the project's `.env` file right away — before deploying config or writing app code. Both `service.yaml` (via `!env` tags) and app code (e.g. `fetchCredentials`) depend on these values. If they are not in `.env`, the PowerSync config will deploy with broken connection details and the app will not connect. Include at minimum: `POWERSYNC_URL`, the Postgres connection URI (e.g. `PS_DATABASE_URI`), and any backend-specific keys. +7. **Create/link the instance and deploy config before writing app code.** Use the CLI — do not create config files manually. For Cloud: `powersync init cloud` → edit config → `powersync link cloud --create` → `powersync deploy`. For self-hosted: `powersync init self-hosted` → `powersync docker configure` → `powersync docker start`. For source database setup the agent cannot run (e.g. Supabase publication SQL), present the exact SQL and ask the user to confirm it is done. The app will not sync without deployed config. +8. Only after backend readiness is confirmed, implement app-side PowerSync integration. Do not start client-side debugging while the PowerSync service is still unconfigured. If the UI is stuck on `Syncing...`, the default diagnosis is incomplete backend setup, not a frontend bug. ## Critical Footguns -Apply these rules without exception: +These apply to all paths. Domain-specific pitfalls are documented in the relevant reference files — only load those when working on that domain. -- `powersync/service.yaml` uses `replication.connections`, not a top-level `connections` key. -- `powersync/sync-config.yaml` must begin with: - ```yaml - config: - edition: 3 - ``` -- `powersync pull instance` silently overwrites local `service.yaml` and `sync-config.yaml`. -- For existing Cloud instances, pull config before manual edits. Never pull after editing unless you have backed up the local files. -- The self-hosted Docker image listens on port **8080**, not 80. Use `-p 8080:8080` in port mapping. -- The Docker image does **not** accept a `-s` flag for sync config. Use the `POWERSYNC_SYNC_CONFIG_B64` environment variable or the `-sync64` flag. -- For local Postgres / local Supabase, set `sslmode: disable` as a YAML key on the connection — the `sslmode=disable` URI query string is ignored by pgwire. +- After any CLI operation that provisions or links a service (Supabase, PowerSync, or any backend), immediately write the resulting credentials and URLs to the project `.env` file. Do not defer this — downstream config and app code read from `.env` and will break silently if values are missing. +- `powersync pull instance` silently overwrites local `service.yaml` and `sync-config.yaml`. Always back up before pulling. -## Default Benchmark Path - -For a React web app using Supabase auth and PowerSync Cloud, load these files in this order: - -1. `references/onboarding-supabase-web.md` -2. `references/supabase-auth.md` -3. `references/powersync-cli.md` -4. `references/powersync-service.md` -5. `references/sync-config.md` -6. `references/sdks/powersync-js.md` -7. `references/sdks/powersync-js-react.md` - -Use the onboarding recipe as the primary workflow. Use the other references to fill in details, not to invent a different sequence. +Additional footguns are in their reference files — do not load these unless working in that area: +- **Config/CLI:** `references/powersync-cli.md`, `references/powersync-service.md`, `references/sync-config.md` +- **JS/TS SDK:** `references/sdks/powersync-js.md` (type-only imports, connect() semantics, transaction.complete()) +- **React:** `references/sdks/powersync-js-react.md` (Strict Mode, Suspense, Next.js) +- **Supabase:** `references/onboarding-supabase-web.md` (CLI limitations, publication SQL) ## Required Inputs Before Coding Collect the minimum required information for the chosen path before changing app code. -### Cloud + Supabase +### All paths +- Which backend/database (do not assume Supabase — ask if not specified) - Whether the PowerSync instance already exists -- **Whether Supabase is online (hosted at supabase.com) or locally hosted** (e.g. `supabase start`) — if you cannot infer this from the project or env, **prompt the user** - PowerSync instance URL, if an instance already exists - Project ID and instance ID, if using CLI with an existing instance -- Supabase Postgres connection string, if PowerSync still needs the source DB connection +- Source database connection string, if PowerSync still needs the source DB connection + +### Additional for Supabase + +- **Whether Supabase is online (hosted at supabase.com) or locally hosted** (e.g. `supabase start`) — if you cannot infer this from the project or env, **prompt the user** - Whether Supabase JWT signing uses new signing keys or legacy JWT secret, if not obvious from the setup +### Additional for custom backends + +- How the user wants to handle auth (custom JWT, third-party auth provider) +- Whether they have an existing backend API or need to create one (load `references/custom-backend.md`) + Only ask for secrets when you are at the step that actually needs them. ## Cloud Readiness Gate @@ -100,10 +95,11 @@ Do not proceed to app-side code until all items below are verified: - Client auth is configured - Instance URL is available for `fetchCredentials()` - Source database replication/publication setup is complete +- All credentials and URLs are persisted in `.env` (e.g. `POWERSYNC_URL`, `PS_DATABASE_URI`, and any backend-specific keys) If any item is missing, finish the service setup first. -Use the CLI to verify and complete any missing items. For steps the agent cannot perform (e.g. running SQL in Supabase SQL Editor), present the exact commands and ask the user to confirm completion before writing app code. +Use the CLI to verify and complete any missing items. For steps the agent cannot perform (e.g. running SQL in the database), present the exact commands and ask the user to confirm completion before writing app code. ## First Response for `Syncing...` @@ -113,7 +109,7 @@ When the app shows `Syncing...`, check these in order before asking for browser 2. Confirm the PowerSync service has a valid source DB connection. 3. Confirm sync config is deployed and uses `config: edition: 3`. 4. Confirm client auth is configured correctly. -5. Confirm the Supabase publication exists for the synced tables. +5. Confirm the source database publication/replication is set up for the synced tables. 6. Only then inspect frontend connector or SDK state. Before requesting console logs, ask the user to confirm: @@ -122,7 +118,7 @@ Before requesting console logs, ask the user to confirm: - whether database connection is configured - whether sync config was deployed - whether client auth was enabled -- whether the Supabase SQL was run +- whether the source database publication/replication SQL was run ## Setup Paths @@ -198,18 +194,17 @@ Key rule: **client writes never go through PowerSync**. They go from the app's u ## What to Load for Your Task -| Task | Load these files | -|------|-----------------| -| React web app + Supabase + Cloud onboarding | `references/onboarding-supabase-web.md` + `references/supabase-auth.md` + `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` + `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` | -| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` + SDK files for your platform | -| Setting up PowerSync with Supabase | `references/onboarding-supabase-web.md` + `references/supabase-auth.md` | -| Debugging sync / connection issues | `references/powersync-debug.md` | -| Writing or migrating sync config | `references/sync-config.md` | -| Configuring the service / self-hosting | `references/powersync-service.md` + `references/powersync-cli.md` | -| Using the PowerSync CLI | `references/powersync-cli.md` + `references/sync-config.md` | -| Handling file uploads / attachments | `references/attachments.md` | -| Custom backend (non-Supabase) | `references/custom-backend.md` + `references/powersync-service.md` + `references/sync-config.md` + SDK files for your platform | -| Understanding the overall architecture | This file is sufficient; see `references/powersync-overview.md` for deep links | +| Task | Start with | Load on demand | +|------|-----------|----------------| +| React web + Supabase + Cloud | `references/onboarding-supabase-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/supabase-auth.md`, `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` (when writing app code) | +| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` | SDK files for your platform (when writing app code) | +| PowerSync + Supabase | `references/onboarding-supabase-web.md` + `references/sync-config.md` | `references/supabase-auth.md` (when configuring auth) | +| Debugging sync issues | `references/powersync-debug.md` | — | +| Writing sync config | `references/sync-config.md` | — | +| Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` + `references/sync-config.md` | — | +| Attachments | `references/attachments.md` | — | +| Custom backend (non-Supabase) | `references/custom-backend.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/powersync-service.md`, SDK files (when writing app code) | +| Architecture overview | This file is sufficient | `references/powersync-overview.md` for deep links | ## SDK Reference Files diff --git a/skills/powersync/references/onboarding-supabase-web.md b/skills/powersync/references/onboarding-supabase-web.md index 90d5af8..49cce4f 100644 --- a/skills/powersync/references/onboarding-supabase-web.md +++ b/skills/powersync/references/onboarding-supabase-web.md @@ -23,19 +23,22 @@ Collect these before editing app code: Only ask for the Postgres connection string when you are at the service configuration step. +**Note:** The Supabase CLI (`supabase init`, `supabase link`) does **not** create a new Supabase project — it only scaffolds local config or links to an existing one. The user must create the project via the Supabase dashboard first. + ## Workflow Follow this sequence exactly. **Prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md)** (see `references/powersync-cli.md`) as the first option to create/link the instance and to deploy service config and sync config. Try running the CLI commands directly before sending the user to the dashboard. 1. Confirm the path is PowerSync Cloud + Supabase + web app. -2. Generate the sync config and Supabase SQL based on the app's tables. -3. **Run the Supabase publication SQL before deploying.** The publication must exist before PowerSync connects to the database — deploying without it causes replication errors. Present the exact SQL to the user and ask them to run it in the Supabase SQL Editor and confirm when done. -4. **Deploy backend setup before writing app code:** +2. **Write all credentials to `.env` immediately.** As soon as Supabase project details are available (from CLI output or dashboard), write `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `PS_DATABASE_URI` (Postgres connection string), and `POWERSYNC_URL` to the project `.env` file. Both `service.yaml` (via `!env` tags) and app code depend on these values — if they are missing, config deploys will use broken placeholders and the app will not connect. +3. Generate the sync config and Supabase SQL based on the app's tables. +4. **Run the Supabase publication SQL before deploying.** The publication must exist before PowerSync connects to the database — deploying without it causes replication errors. Present the exact SQL to the user and ask them to run it in the Supabase SQL Editor and confirm when done. +5. **Deploy backend setup before writing app code:** - Use `powersync deploy sync-config` and `powersync deploy service-config` to deploy directly via CLI. - Do not defer deployment to a post-implementation summary — the app will not sync without a deployed sync config. -5. Verify backend readiness. -6. Only then implement app-side PowerSync integration. -7. If the UI is stuck on `Syncing...`, re-check backend readiness before touching frontend code. +6. Verify backend readiness. +7. Only then implement app-side PowerSync integration. +8. If the UI is stuck on `Syncing...`, re-check backend readiness before touching frontend code. ### Sync Config Deployment @@ -65,6 +68,7 @@ Do not move on until all items below are true: - Client auth is configured for Supabase - PowerSync instance URL is known - Supabase publication exists for the synced tables +- All credentials and URLs are in `.env` (`POWERSYNC_URL`, `PS_DATABASE_URI`, `SUPABASE_URL`, `SUPABASE_ANON_KEY`) ## New Cloud Instance diff --git a/skills/powersync/references/sdks/powersync-js-react.md b/skills/powersync/references/sdks/powersync-js-react.md index 91520d8..e31f463 100644 --- a/skills/powersync/references/sdks/powersync-js-react.md +++ b/skills/powersync/references/sdks/powersync-js-react.md @@ -293,6 +293,38 @@ export default defineConfig({ ## Common Pitfalls +### React Strict Mode destroys PowerSyncDatabase in useEffect + +In development, React Strict Mode unmounts and remounts every component. If you create a `PowerSyncDatabase` inside a `useEffect` cleanup/setup cycle, the first mount's cleanup releases the shared-worker DB proxy before the second mount can use it — the database connection silently breaks. + +```tsx +// WRONG — Strict Mode will destroy this on the dev double-mount +function App() { + const [db, setDb] = useState(null); + useEffect(() => { + const database = new PowerSyncDatabase({ schema, database: { dbFilename: 'app.db' } }); + database.connect(connector); + setDb(database); + return () => { database.close(); }; // Kills the DB on Strict Mode re-mount + }, []); + // ... +} + +// CORRECT — create once at module scope (or use a stable singleton) +const db = new PowerSyncDatabase({ schema, database: { dbFilename: 'app.db' } }); +db.connect(connector); + +function App() { + return ( + + + + ); +} +``` + +Keep the DB instance stable across transient remounts. Only call `db.close()` when the app is truly done with it (e.g. on logout with `disconnectAndClear()`). Disabling `enableMultiTabs` can mask the symptom temporarily but does not fix the root cause. + ### Suspense requires ErrorBoundary `useSuspenseQuery` throws query errors upward — they go to the nearest ``, not ``. Without an ErrorBoundary, query errors crash the component tree silently. From c2e65bf800fd4d10464fbe2471dfc4f7aa21b278 Mon Sep 17 00:00:00 2001 From: bean1352 Date: Wed, 18 Mar 2026 14:47:10 +0200 Subject: [PATCH 2/9] Clarify Supabase CLI project creation note in onboarding documentation --- skills/powersync/references/onboarding-supabase-web.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/powersync/references/onboarding-supabase-web.md b/skills/powersync/references/onboarding-supabase-web.md index 49cce4f..f23ea6e 100644 --- a/skills/powersync/references/onboarding-supabase-web.md +++ b/skills/powersync/references/onboarding-supabase-web.md @@ -23,7 +23,7 @@ Collect these before editing app code: Only ask for the Postgres connection string when you are at the service configuration step. -**Note:** The Supabase CLI (`supabase init`, `supabase link`) does **not** create a new Supabase project — it only scaffolds local config or links to an existing one. The user must create the project via the Supabase dashboard first. +**Note:** The Supabase CLI (`supabase init`, `supabase link`) does **not** create a new online Supabase project — it only scaffolds local config or links to an existing one. The user must create the project via the Supabase dashboard first. ## Workflow From bbfd60544f5dbcff533e896791baf428a6119732 Mon Sep 17 00:00:00 2001 From: bean1352 Date: Wed, 18 Mar 2026 15:28:46 +0200 Subject: [PATCH 3/9] Emphasize strict adherence to the playbook in AGENTS.md. --- .cursor/rules/powersync-playbook.mdc | 17 +++++++++++++++ AGENTS.md | 13 ++++++++++-- CLAUDE.md | 2 +- CONTRIBUTING.md | 10 +++++++++ skills/powersync/AGENTS.md | 18 +++++++++++++++- skills/powersync/SKILL.md | 4 +++- .../references/onboarding-supabase-web.md | 6 +++--- skills/powersync/references/powersync-cli.md | 21 ++++++++++++++++--- 8 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 .cursor/rules/powersync-playbook.mdc diff --git a/.cursor/rules/powersync-playbook.mdc b/.cursor/rules/powersync-playbook.mdc new file mode 100644 index 0000000..4b5f2cd --- /dev/null +++ b/.cursor/rules/powersync-playbook.mdc @@ -0,0 +1,17 @@ +--- +description: PowerSync skill — follow AGENTS.md playbook; powersync login is Cloud-only +globs: + - skills/powersync/**/* + - AGENTS.md + - CONTRIBUTING.md +--- + +# PowerSync agent skills + +When editing or when the user’s task involves PowerSync: + +1. Open **skills/powersync/AGENTS.md** and follow **Agent compliance** and the numbered onboarding playbook. Do not assume backend or Cloud vs self-hosted. +2. Prefer the **PowerSync CLI** per that file; do not hand-roll deployable config unless the user explicitly cannot use the CLI. +3. **`powersync login`** is for **PowerSync Cloud** (PAT) only — not for authenticating to a **self-hosted** service. Self-hosted: `powersync init self-hosted`, Docker commands, `PS_ADMIN_TOKEN` on the service. + +If instructions here conflict with a “faster” approach, the playbook wins unless the user explicitly overrides. diff --git a/AGENTS.md b/AGENTS.md index a8f5b0d..5ccbd4e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,14 @@ # PowerSync Agent Skills -If you are building an application with PowerSync, start here: [skills/powersync/AGENTS.md](skills/powersync/AGENTS.md) +If you are building or modifying anything that uses **PowerSync**, you **must** follow the playbook in **[skills/powersync/AGENTS.md](skills/powersync/AGENTS.md)** — not a condensed summary. -That file contains the full onboarding playbook, setup paths, SDK references, and debugging guides. +That file includes: + +- **Agent compliance (non-negotiable):** ask Cloud vs self-hosted, ask backend if unspecified, CLI-first, no silent shortcuts. +- Full onboarding sequence, setup paths, SDK references, and debugging. + +**Do not** assume Supabase, assume self-hosted Docker, or skip CLI steps unless the user **explicitly** opts out. + +**`powersync login`** authenticates the CLI to **PowerSync Cloud** only (personal access token). It is **not** how you “log in” to a **self-hosted** PowerSync service. Self-hosted doesnt not require login + +When editing files under `skills/powersync/`, preserve and strengthen playbook language so agents cannot reasonably treat references as optional recipes. diff --git a/CLAUDE.md b/CLAUDE.md index bf038a3..32210f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1 @@ -See [AGENTS.md](AGENTS.md) for guidance. +See [AGENTS.md](AGENTS.md) for guidance. For PowerSync work, follow [skills/powersync/AGENTS.md](skills/powersync/AGENTS.md) in full — including **Agent compliance**. **`powersync login`** = PowerSync Cloud only. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a04488..bdbe174 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,16 @@ skills/powersync/ └── powersync-swift.md ``` +## Writing for agents (playbook enforcement) + +Agents often skip long docs. When you change this skill: + +1. **Keep [skills/powersync/AGENTS.md](skills/powersync/AGENTS.md) as the single source of truth** for onboarding order. Put non-negotiable rules at the top (see **Agent compliance**). +2. **Duplicate critical constraints** in `SKILL.md` (first screen for many installers) and in `references/powersync-cli.md` when the topic is CLI/auth. +3. **Root [AGENTS.md](AGENTS.md)** should stay short but **mandatory**: point to the full playbook and forbid silent shortcuts. + +Optional for Cursor users in this repo: [.cursor/rules/powersync-playbook.mdc](.cursor/rules/powersync-playbook.mdc) reinforces the same rules when editing skill files. + ## Types of Contributions ### Bug fixes diff --git a/skills/powersync/AGENTS.md b/skills/powersync/AGENTS.md index 2495fe0..459e22b 100644 --- a/skills/powersync/AGENTS.md +++ b/skills/powersync/AGENTS.md @@ -2,6 +2,20 @@ Use this skill to onboard a project onto PowerSync without trial-and-error. Treat this as a guided workflow first and a reference library second. +## Agent compliance (read first — non-negotiable) + +**Follow this file’s playbook in order.** Do not skip ahead, assume defaults, or substitute your own architecture to “save time.” + +| Do | Don’t | +|----|--------| +| Ask for **Cloud vs self-hosted** and **which backend** if the user did not say | Assume Supabase, assume Postgres, or pick self-hosted Docker without asking | +| Use the **PowerSync CLI** to scaffold, link (if cloud hosted), and deploy (`references/powersync-cli.md`) | Hand-write `service.yaml` / `sync-config.yaml` from scratch or invent compose files **unless** the user explicitly says they cannot use the CLI | +| **Stop and ask** when a step needs credentials or interactive Cloud login you cannot perform | Silently build an alternate stack (e.g. manual Docker) without user confirmation | +| Complete **backend readiness** (deployed sync config, auth, publication) **before** app code | Start React/client integration while sync is still unconfigured | + +If the user wants a shortcut, they must **say so explicitly** (e.g. “I can’t use the CLI, give dashboard steps only”). + + ## Always Use the PowerSync CLI **The [PowerSync CLI](https://docs.powersync.com/tools/cli.md) is the default tool for all PowerSync operations.** Do not manually create config files, do not direct users to the dashboard, and do not write service.yaml or sync-config.yaml from scratch. The CLI handles all of this. @@ -119,7 +133,7 @@ Load `references/powersync-cli.md` and prefer the CLI for every step it supports - Create and link the instance: `powersync link cloud --create --project-id=` - Deploy service config: `powersync deploy service-config` - Deploy sync config: `powersync deploy sync-config` -- Prefer `PS_ADMIN_TOKEN` in autonomous or noninteractive environments; use `powersync login` only when interactive auth is acceptable +- Prefer `PS_ADMIN_TOKEN` in autonomous or noninteractive environments; use **`powersync login` only for Cloud** (stores a Cloud PAT), and only when interactive auth is acceptable ### Path 2: Cloud + Dashboard @@ -138,6 +152,8 @@ If the backend is Supabase, also load `references/supabase-auth.md`. ### Path 3: Self-Hosted + CLI (Recommended) +**Not Cloud:** do not use **`powersync login`** as the way to “log in” to self-hosted — that command stores a **PowerSync Cloud** PAT. Self-hosted uses **`powersync init self-hosted`**, **`powersync docker configure`**, **`powersync docker start`**, and the service’s **`PS_ADMIN_TOKEN`** for admin API access. + Load `references/powersync-cli.md`, `references/powersync-service.md`, and `references/sync-config.md`. Prefer the CLI for Docker runs (`powersync docker run`, `powersync docker reset`), schema generation, and any supported self-hosted operations. See [PowerSync CLI](https://docs.powersync.com/tools/cli.md). ### Path 4: Self-Hosted + Manual Docker diff --git a/skills/powersync/SKILL.md b/skills/powersync/SKILL.md index 5bfedd2..40abfd6 100644 --- a/skills/powersync/SKILL.md +++ b/skills/powersync/SKILL.md @@ -14,6 +14,8 @@ metadata: Use this skill to onboard a project onto PowerSync without trial-and-error. Treat this as a guided workflow first and a reference library second. +**Agents:** Follow **[AGENTS.md](AGENTS.md)** in order — including **Agent compliance** (ask Cloud vs self-hosted, ask backend if unspecified, CLI-first, no silent shortcuts). **`powersync login`** is **PowerSync Cloud only** (PAT); self-hosted does not use it for the running service. See `references/powersync-cli.md` → Authentication. + ## Always Use the PowerSync CLI **The [PowerSync CLI](https://docs.powersync.com/tools/cli.md) is the default tool for all PowerSync operations.** Do not manually create config files, do not direct users to the dashboard, and do not write service.yaml or sync-config.yaml from scratch. The CLI handles all of this. @@ -131,7 +133,7 @@ Load `references/powersync-cli.md` and prefer the CLI for every step it supports - Create and link the instance: `powersync link cloud --create --project-id=` - Deploy service config: `powersync deploy service-config` - Deploy sync config: `powersync deploy sync-config` -- Prefer `PS_ADMIN_TOKEN` in autonomous or noninteractive environments; use `powersync login` only when interactive auth is acceptable +- Prefer `PS_ADMIN_TOKEN` in autonomous or noninteractive environments; use **`powersync login` only for PowerSync Cloud** (interactive PAT), not for self-hosted-only workflows ### Path 2: Cloud + Dashboard diff --git a/skills/powersync/references/onboarding-supabase-web.md b/skills/powersync/references/onboarding-supabase-web.md index f23ea6e..937fc8d 100644 --- a/skills/powersync/references/onboarding-supabase-web.md +++ b/skills/powersync/references/onboarding-supabase-web.md @@ -19,7 +19,7 @@ Collect these before editing app code: - PowerSync instance URL, if the instance already exists - Project ID and instance ID, if using CLI with an existing instance - Supabase Postgres connection string, if the PowerSync source DB connection is not already configured -- `PS_ADMIN_TOKEN` or willingness to run `powersync login` (for CLI deployment) +- `PS_ADMIN_TOKEN` or willingness to run **`powersync login`** (**PowerSync Cloud** PAT only — not used for self-hosted stacks) Only ask for the Postgres connection string when you are at the service configuration step. @@ -80,7 +80,7 @@ Prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md) for every st ```bash PS_ADMIN_TOKEN=your-token-here powersync fetch instances ``` - If no token is available, use `powersync login` and treat it as interactive. + If no token is available, use **`powersync login`** (Cloud PAT) and treat it as interactive. Skip this for self-hosted-only paths. 2. Scaffold: ```bash powersync init cloud @@ -118,7 +118,7 @@ Never run `powersync pull instance` after editing local config. If you need to p ### CLI path (Recommended) -1. Authenticate with `PS_ADMIN_TOKEN` if available, otherwise `powersync login`. +1. Authenticate to **PowerSync Cloud** with `PS_ADMIN_TOKEN` if available, otherwise **`powersync login`** (Cloud PAT). 2. Pull config before editing: ```bash powersync pull instance --project-id= --instance-id= diff --git a/skills/powersync/references/powersync-cli.md b/skills/powersync/references/powersync-cli.md index 0a4de63..7d4c56f 100644 --- a/skills/powersync/references/powersync-cli.md +++ b/skills/powersync/references/powersync-cli.md @@ -14,6 +14,7 @@ The PowerSync CLI manages Cloud and self-hosted PowerSync instances from the com Use these defaults unless the user explicitly wants something else: - Prefer `PS_ADMIN_TOKEN` in autonomous or noninteractive environments. +- **`powersync login` is Cloud-only** (stores a Cloud PAT). Do not present it as the auth path for self-hosted-only setups. - Treat `powersync login` as interactive and likely to interrupt the flow. - Prefer `powersync deploy service-config` or `powersync deploy sync-config` over `powersync deploy` when only one file changed. - For existing Cloud instances, pull config before manual edits and never pull again after editing unless local files were backed up first. @@ -58,17 +59,31 @@ For Cloud, `--org-id` / `ORG_ID` is optional — omit it when your token has acc ## Authentication +### `powersync login` is for PowerSync Cloud only + +**`powersync login`** stores a **PowerSync Cloud** personal access token (PAT). It authenticates the CLI against the **hosted PowerSync API** (create/link Cloud instances, `powersync deploy` to Cloud, `powersync fetch instances`, etc.). It is **not** used to authenticate to a **self-hosted** PowerSync service running in Docker. + +| Hosting | How the CLI authenticates | +|---------|---------------------------| +| **PowerSync Cloud** | `PS_ADMIN_TOKEN` (PAT) or token from **`powersync login`** | +| **Self-hosted** | No `powersync login` for the running service. Use **`powersync init self-hosted`**, **`powersync docker configure` / `powersync docker start`**, and **`PS_ADMIN_TOKEN`** matching the self-hosted service’s admin API token (see self-hosted docs). | + +Do not tell users to run `powersync login` when they are **only** using a local self-hosted stack unless they also need Cloud CLI commands. + +--- + Cloud commands require a PowerSync personal access token (PAT). If the user does not have one, direct them to generate one at: https://dashboard.powersync.com/account/access-tokens Prefer `PS_ADMIN_TOKEN` when the environment is noninteractive or when the agent should avoid browser/device-login interruptions. -The CLI checks in this order: +The CLI checks in this order (**for Cloud API calls**): 1. `PS_ADMIN_TOKEN` environment variable -2. Token stored via `powersync login` (macOS Keychain or config-file fallback) +2. Token stored via **`powersync login`** (macOS Keychain or config-file fallback) — **Cloud PAT only** ```bash -# Store token for local use — opens browser to create a token or paste an existing one +# Store a PowerSync Cloud PAT for local use — opens browser or paste token +# Not applicable to self-hosted-only workflows. powersync login # CI / one-off — set env var From f15a205e0df513ff92f5fac00d7e2152388ab3d6 Mon Sep 17 00:00:00 2001 From: bean1352 Date: Tue, 24 Mar 2026 14:09:48 +0200 Subject: [PATCH 4/9] Update onboarding documentation and references for custom backend integration --- .gitignore | 4 +- skills/powersync/AGENTS.md | 4 +- skills/powersync/SKILL.md | 230 +++--------------- .../references/onboarding-custom-web.md | 208 ++++++++++++++++ .../powersync/references/powersync-service.md | 25 +- skills/powersync/references/sync-config.md | 2 +- 6 files changed, 264 insertions(+), 209 deletions(-) create mode 100644 skills/powersync/references/onboarding-custom-web.md diff --git a/.gitignore b/.gitignore index de3167a..79357fc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ node_modules/ .vscode /powersync /temp -/temp-claude \ No newline at end of file +/temp-claude +/powersync-workspace +**/evals/ \ No newline at end of file diff --git a/skills/powersync/AGENTS.md b/skills/powersync/AGENTS.md index 459e22b..6849491 100644 --- a/skills/powersync/AGENTS.md +++ b/skills/powersync/AGENTS.md @@ -40,7 +40,7 @@ When the task is to add PowerSync to an app, follow this sequence in order: 1. Identify the platform: **Cloud** or **self-hosted**. 2. **Identify the backend.** If the user has not specified a backend, **ask them** which database/backend they want to use (e.g. Supabase, custom Postgres, MongoDB, MySQL, MSSQL). Do not assume Supabase. The choice determines which references to load: - **Supabase** → load `references/onboarding-supabase-web.md` + `references/supabase-auth.md` - - **Any other backend** → load `references/custom-backend.md` — the agent must create a backend API with an `uploadData`, `token` endpoint and a JWT auth provider. Do not skip this. + - **Any other backend (web)** → load `references/onboarding-custom-web.md` + `references/custom-backend.md` — the agent must create a backend API with an `uploadData`, `token` endpoint and a JWT auth provider. Do not skip this. 3. If the backend is Supabase and it is unclear whether the user means **online (Supabase Cloud)** or **locally hosted** (e.g. `supabase start`), **ask the user** before choosing connection strings, auth config, or references. 4. Collect required inputs before coding. 5. **Always load `references/sync-config.md`** and generate sync config. Also set up any required source database configuration (e.g. Supabase publication SQL, Postgres publication, MongoDB replica set). Sync config is mandatory for every PowerSync project — without it, nothing syncs. @@ -207,7 +207,7 @@ Key rule: **client writes never go through PowerSync**. They go from the app's u | Writing sync config | `references/sync-config.md` | — | | Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` + `references/sync-config.md` | — | | Attachments | `references/attachments.md` | — | -| Custom backend (non-Supabase) | `references/custom-backend.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/powersync-service.md`, SDK files (when writing app code) | +| Web + custom backend (non-Supabase) | `references/onboarding-custom-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/custom-backend.md`, `references/powersync-service.md`, SDK files (when writing app code) | | Architecture overview | This file is sufficient | `references/powersync-overview.md` for deep links | ## SDK Reference Files diff --git a/skills/powersync/SKILL.md b/skills/powersync/SKILL.md index 40abfd6..49e2070 100644 --- a/skills/powersync/SKILL.md +++ b/skills/powersync/SKILL.md @@ -1,212 +1,41 @@ --- name: powersync -description: Guided onboarding and best practices for building applications with PowerSync — Cloud and self-hosted setup, sync configuration, client SDK usage, Supabase integration, and debugging. +description: Guided onboarding and best practices for building applications with PowerSync — Cloud and self-hosted setup, sync configuration, client SDK usage, backend integration (Supabase, custom Postgres, MongoDB, MySQL, MSSQL), and debugging. license: MIT compatibility: Works with any skills-compatible agent. Some references include CLI commands requiring the @powersync/cli package. metadata: author: powersync - version: "1.1.0" + version: "1.2.0" organization: PowerSync - tags: powersync, offline-first, local-first, sync-streams, sqlite, replication, supabase, uploadData, fetchCredentials, service-config, sync-config, cloud, cli, debugging + tags: powersync, offline-first, local-first, sync-streams, sqlite, replication, uploadData, fetchCredentials, service-config, sync-config, cloud, cli, debugging, supabase, postgres, mongodb, mysql --- # PowerSync Skills Use this skill to onboard a project onto PowerSync without trial-and-error. Treat this as a guided workflow first and a reference library second. -**Agents:** Follow **[AGENTS.md](AGENTS.md)** in order — including **Agent compliance** (ask Cloud vs self-hosted, ask backend if unspecified, CLI-first, no silent shortcuts). **`powersync login`** is **PowerSync Cloud only** (PAT); self-hosted does not use it for the running service. See `references/powersync-cli.md` → Authentication. +**Agents:** Follow **[AGENTS.md](AGENTS.md)** in full — including **Agent compliance** (ask Cloud vs self-hosted, ask backend if unspecified, CLI-first, no silent shortcuts). **`powersync login`** is **PowerSync Cloud only** (PAT); self-hosted does not use it. -## Always Use the PowerSync CLI +## Quick Rules -**The [PowerSync CLI](https://docs.powersync.com/tools/cli.md) is the default tool for all PowerSync operations.** Do not manually create config files, do not direct users to the dashboard, and do not write service.yaml or sync-config.yaml from scratch. The CLI handles all of this. - -What the CLI does — use it instead of doing these things manually: - -- **Create a Cloud instance:** `powersync init cloud` → `powersync link cloud --create --project-id=` (scaffolds config, creates the instance, and links it in one flow) -- **Link an existing Cloud instance:** `powersync pull instance --project-id= --instance-id=` (downloads config to local files) -- **Create a local self-hosted instance:** `powersync init self-hosted` → `powersync docker configure` → `powersync docker start` (spins up a full local PowerSync stack via Docker — no manual Docker setup needed) -- **Deploy config:** `powersync deploy service-config`, `powersync deploy sync-config` (validates and deploys — don't copy-paste YAML to a dashboard) -- **Generate client schema:** `powersync generate schema --output=ts` (generates TypeScript schema from deployed sync config — don't write it by hand) -- **Generate dev tokens:** `powersync generate token --subject=user-1` (for local testing — don't hardcode JWTs) - -Only fall back to manual config or dashboard instructions when the user explicitly says they can't use the CLI. - -Full CLI reference: `references/powersync-cli.md` — **always load this file** when setting up or modifying a PowerSync instance. - -## Onboarding Playbook - -When the task is to add PowerSync to an app, follow this sequence in order: - -1. Identify the platform: **Cloud** or **self-hosted**. -2. **Identify the backend.** If the user has not specified a backend, **ask them** which database/backend they want to use (e.g. Supabase, custom Postgres, MongoDB, MySQL). Do not assume Supabase. The choice determines which references to load: - - **Supabase** → load `references/onboarding-supabase-web.md` + `references/supabase-auth.md` - - **Any other backend** → load `references/custom-backend.md` — the agent must create a backend API with an `uploadData` endpoint and a JWT auth provider. Do not skip this. -3. If the backend is Supabase and it is unclear whether the user means **online (Supabase Cloud)** or **locally hosted** (e.g. `supabase start`), **ask the user** before choosing connection strings, auth config, or references. -4. Collect required inputs before coding. -5. **Always load `references/sync-config.md`** and generate sync config. Also set up any required source database configuration (e.g. Supabase publication SQL, Postgres publication, MongoDB replica set). Sync config is mandatory for every PowerSync project — without it, nothing syncs. -6. **Persist all credentials and connection details to `.env` immediately.** When a CLI or dashboard provides database credentials (host, port, database name, username, password, connection URI), write them to the project's `.env` file right away — before deploying config or writing app code. Both `service.yaml` (via `!env` tags) and app code (e.g. `fetchCredentials`) depend on these values. If they are not in `.env`, the PowerSync config will deploy with broken connection details and the app will not connect. Include at minimum: `POWERSYNC_URL`, the Postgres connection URI (e.g. `PS_DATABASE_URI`), and any backend-specific keys. -7. **Create/link the instance and deploy config before writing app code.** Use the CLI — do not create config files manually. For Cloud: `powersync init cloud` → edit config → `powersync link cloud --create` → `powersync deploy`. For self-hosted: `powersync init self-hosted` → `powersync docker configure` → `powersync docker start`. For source database setup the agent cannot run (e.g. Supabase publication SQL), present the exact SQL and ask the user to confirm it is done. The app will not sync without deployed config. -8. Only after backend readiness is confirmed, implement app-side PowerSync integration. - -Do not start client-side debugging while the PowerSync service is still unconfigured. If the UI is stuck on `Syncing...`, the default diagnosis is incomplete backend setup, not a frontend bug. - -## Critical Footguns - -These apply to all paths. Domain-specific pitfalls are documented in the relevant reference files — only load those when working on that domain. - -- After any CLI operation that provisions or links a service (Supabase, PowerSync, or any backend), immediately write the resulting credentials and URLs to the project `.env` file. Do not defer this — downstream config and app code read from `.env` and will break silently if values are missing. -- `powersync pull instance` silently overwrites local `service.yaml` and `sync-config.yaml`. Always back up before pulling. - -Additional footguns are in their reference files — do not load these unless working in that area: -- **Config/CLI:** `references/powersync-cli.md`, `references/powersync-service.md`, `references/sync-config.md` -- **JS/TS SDK:** `references/sdks/powersync-js.md` (type-only imports, connect() semantics, transaction.complete()) -- **React:** `references/sdks/powersync-js-react.md` (Strict Mode, Suspense, Next.js) -- **Supabase:** `references/onboarding-supabase-web.md` (CLI limitations, publication SQL) - -## Required Inputs Before Coding - -Collect the minimum required information for the chosen path before changing app code. - -### All paths - -- Which backend/database (do not assume Supabase — ask if not specified) -- Whether the PowerSync instance already exists -- PowerSync instance URL, if an instance already exists -- Project ID and instance ID, if using CLI with an existing instance -- Source database connection string, if PowerSync still needs the source DB connection - -### Additional for Supabase - -- **Whether Supabase is online (hosted at supabase.com) or locally hosted** (e.g. `supabase start`) — if you cannot infer this from the project or env, **prompt the user** -- Whether Supabase JWT signing uses new signing keys or legacy JWT secret, if not obvious from the setup - -### Additional for custom backends - -- How the user wants to handle auth (custom JWT, third-party auth provider) -- Whether they have an existing backend API or need to create one (load `references/custom-backend.md`) - -Only ask for secrets when you are at the step that actually needs them. - -## Cloud Readiness Gate - -Do not proceed to app-side code until all items below are verified: - -- PowerSync instance exists -- Source database connection is configured -- Sync config is deployed -- Client auth is configured -- Instance URL is available for `fetchCredentials()` -- Source database replication/publication setup is complete -- All credentials and URLs are persisted in `.env` (e.g. `POWERSYNC_URL`, `PS_DATABASE_URI`, and any backend-specific keys) - -If any item is missing, finish the service setup first. - -Use the CLI to verify and complete any missing items. For steps the agent cannot perform (e.g. running SQL in the database), present the exact commands and ask the user to confirm completion before writing app code. - -## First Response for `Syncing...` - -When the app shows `Syncing...`, check these in order before asking for browser console logs: - -1. Confirm the PowerSync endpoint URL returned by `fetchCredentials()`. -2. Confirm the PowerSync service has a valid source DB connection. -3. Confirm sync config is deployed and uses `config: edition: 3`. -4. Confirm client auth is configured correctly. -5. Confirm the source database publication/replication is set up for the synced tables. -6. Only then inspect frontend connector or SDK state. - -Before requesting console logs, ask the user to confirm: - -- whether the instance exists -- whether database connection is configured -- whether sync config was deployed -- whether client auth was enabled -- whether the source database publication/replication SQL was run - -## Setup Paths - -Choose the matching path after the preflight. The CLI is the default for all paths. - -### Path 1: Cloud + CLI (Recommended) - -Load `references/powersync-cli.md` and prefer the CLI for every step it supports: - -- Create and link the instance: `powersync link cloud --create --project-id=` -- Deploy service config: `powersync deploy service-config` -- Deploy sync config: `powersync deploy sync-config` -- Prefer `PS_ADMIN_TOKEN` in autonomous or noninteractive environments; use **`powersync login` only for PowerSync Cloud** (interactive PAT), not for self-hosted-only workflows - -### Path 2: Cloud + Dashboard - -Only use this path if the user explicitly prefers the dashboard or the CLI is unavailable. - -Guide the user through the dashboard sequence: - -1. Create or open the PowerSync project and instance. -2. Connect the source database. -3. Deploy sync config. -4. Configure client auth. -5. Copy the instance URL. -6. Verify source database replication/publication setup. - -If the backend is Supabase, also load `references/supabase-auth.md`. - -### Path 3: Self-Hosted + CLI (Recommended) - -Load `references/powersync-cli.md`, `references/powersync-service.md`, and `references/sync-config.md`. Prefer the CLI for Docker runs (`powersync docker run`, `powersync docker reset`), schema generation, and any supported self-hosted operations. See [PowerSync CLI](https://docs.powersync.com/tools/cli.md). - -### Path 4: Self-Hosted + Manual Docker - -Only when the CLI cannot be used. Load `references/powersync-service.md` and `references/sync-config.md`. If the backend is **not** Supabase, also load `references/custom-backend.md`. - -## Architecture - -```mermaid -flowchart LR - - subgraph BACKEND["Your Backend"] - direction TB - DB["Backend Database (Postgres | MongoDB | MySQL | Supabase | …)"] - API["Backend API (Your server / cloud functions)"] - API -- "Applies writes" --> DB - end - - subgraph PS_SERVICE["PowerSync Service"] - direction TB - SYNC["Partial Sync (sync rules filter data per user)"] - end - - subgraph APP["Your App"] - direction TB - SDK["PowerSync SDK"] - SQLITE["In-app SQLite (local replica — reads are instant)"] - QUEUE["Upload Queue (offline write buffer)"] - UI["UI"] - SDK --- SQLITE - SDK --- QUEUE - SQLITE <--> UI - QUEUE <--> UI - end - - DB -- "Replicates changes (CDC / logical replication)" --> PS_SERVICE - PS_SERVICE -- "Streams changes (real-time sync)" --> SDK - QUEUE -- "Uploads writes (when connectivity resumes)" --> API -``` - -Key rule: **client writes never go through PowerSync**. They go from the app's upload queue to your backend API. PowerSync handles the read and sync path only. +- **CLI-first.** Use the [PowerSync CLI](https://docs.powersync.com/tools/cli.md) for all operations. Do not hand-write config files. See `references/powersync-cli.md`. +- **Ask, don't assume.** Ask Cloud vs self-hosted. Ask which backend (Supabase, Postgres, MongoDB, MySQL, MSSQL). Do not default to Supabase. +- **Backend before frontend.** Deploy sync config and verify the service before writing app code. +- **Sync Streams for new projects.** Sync Rules are legacy. +- **Persist credentials immediately.** Write all URLs and keys to `.env` as soon as they are available. ## What to Load for Your Task | Task | Start with | Load on demand | |------|-----------|----------------| -| React web + Supabase + Cloud | `references/onboarding-supabase-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/supabase-auth.md`, `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` (when writing app code) | -| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` | SDK files for your platform (when writing app code) | -| PowerSync + Supabase | `references/onboarding-supabase-web.md` + `references/sync-config.md` | `references/supabase-auth.md` (when configuring auth) | -| Debugging sync issues | `references/powersync-debug.md` | — | -| Writing sync config | `references/sync-config.md` | — | +| New project setup | `references/powersync-cli.md` + `references/sync-config.md` | SDK files for your platform (when writing app code) | +| React web + Supabase + Cloud | `references/onboarding-supabase-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/supabase-auth.md`, `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` | +| Web + custom backend (non-Supabase) | `references/onboarding-custom-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/custom-backend.md`, `references/powersync-service.md`, SDK files | | Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` + `references/sync-config.md` | — | +| Writing sync config | `references/sync-config.md` | — | +| Debugging sync issues | `references/powersync-debug.md` | — | | Attachments | `references/attachments.md` | — | -| Custom backend (non-Supabase) | `references/custom-backend.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/powersync-service.md`, SDK files (when writing app code) | -| Architecture overview | This file is sufficient | `references/powersync-overview.md` for deep links | +| Architecture overview | `references/powersync-overview.md` | — | ## SDK Reference Files @@ -214,26 +43,25 @@ Key rule: **client writes never go through PowerSync**. They go from the app's u Always load `references/sdks/powersync-js.md` for any JS/TS project, then load the applicable framework file. -| Framework | Load when… | File | -|-----------|-----------|------| -| React / Next.js | React web app or Next.js | `references/sdks/powersync-js-react.md` | -| React Native / Expo | React Native, Expo, or Expo Go | `references/sdks/powersync-js-react-native.md` | -| Vue / Nuxt | Vue or Nuxt | `references/sdks/powersync-js-vue.md` | -| Node.js / Electron | Node.js CLI/server or Electron | `references/sdks/powersync-js-node.md` | -| TanStack | TanStack Query or TanStack DB | `references/sdks/powersync-js-tanstack.md` | +| Framework | File | +|-----------|------| +| React / Next.js | `references/sdks/powersync-js-react.md` | +| React Native / Expo | `references/sdks/powersync-js-react-native.md` | +| Vue / Nuxt | `references/sdks/powersync-js-vue.md` | +| Node.js / Electron | `references/sdks/powersync-js-node.md` | +| TanStack | `references/sdks/powersync-js-tanstack.md` | ### Other SDKs -| Platform | Load when… | File | -|----------|-----------|------| -| Dart / Flutter | Dart / Flutter | `references/sdks/powersync-dart.md` | -| .NET | .NET | `references/sdks/powersync-dotnet.md` | -| Kotlin | Kotlin | `references/sdks/powersync-kotlin.md` | -| Swift | Swift / iOS / macOS | `references/sdks/powersync-swift.md` | +| Platform | File | +|----------|------| +| Dart / Flutter | `references/sdks/powersync-dart.md` | +| .NET | `references/sdks/powersync-dotnet.md` | +| Kotlin | `references/sdks/powersync-kotlin.md` | +| Swift | `references/sdks/powersync-swift.md` | ## Key Rules to Apply Without Being Asked -- Use Sync Streams for new projects. Sync Rules are legacy. - Never define the `id` column in a PowerSync table schema; it is created automatically. - Use `column.integer` for booleans and `column.text` for ISO date strings. - `connect()` is fire-and-forget. Use `waitForFirstSync()` if you need readiness. diff --git a/skills/powersync/references/onboarding-custom-web.md b/skills/powersync/references/onboarding-custom-web.md new file mode 100644 index 0000000..0ef6219 --- /dev/null +++ b/skills/powersync/references/onboarding-custom-web.md @@ -0,0 +1,208 @@ +--- +name: onboarding-custom-web +description: Step-by-step onboarding recipe for a web app using a custom backend (non-Supabase) with PowerSync — custom Postgres, custom JWT auth, backend API for uploadData, and client integration +metadata: + tags: onboarding, web, custom, backend, postgres, jwt, auth, uploadData, recipe, cloud, self-hosted +--- + +# Web App + Custom Backend + PowerSync + +Use this file when onboarding a web app onto PowerSync with a **non-Supabase backend** — your own Postgres (or other supported database), your own auth, and your own backend API. This recipe works for both PowerSync Cloud and self-hosted. + +**Strongly prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md) as the first option** for setup. See `references/powersync-cli.md`. Fall back to the dashboard (Cloud) or manual Docker config (self-hosted) only if the CLI is unavailable or the user explicitly prefers it. + +## Required Inputs + +Collect these before writing any code: + +- **Cloud or self-hosted** — which PowerSync hosting model +- **Database type and connection details** — Postgres, MongoDB, MySQL, or MSSQL (host, port, database, username, password or connection URI) +- Whether a PowerSync instance already exists +- PowerSync instance URL, if the instance already exists +- Project ID and instance ID, if using CLI with an existing Cloud instance +- How the user wants to handle auth (custom JWT with own keys, third-party auth provider like Auth0/Firebase, or dev tokens for prototyping) +- Whether they have an existing backend API or need to create one + +Only ask for secrets (database password, private keys) when you are at the step that actually needs them. + +## Workflow + +Follow this sequence exactly. **Do not skip ahead to app code.** + +### Phase 1: Service Setup + +1. **Confirm the path.** Verify: PowerSync (Cloud or self-hosted) + custom backend + web app. + +2. **Set up the source database.** Based on the database type, load `references/powersync-service.md` for the relevant quick start: + - **Postgres:** Enable logical replication (`wal_level = logical`), create a replication user, create a publication for the synced tables, set `REPLICA IDENTITY FULL` on each synced table. + - **MongoDB:** Ensure replica set is initialized, create a user with read access + write access to `_powersync_checkpoints`. + - **MySQL:** Enable binary logging with `ROW` format and GTIDs, create a replication user. + - **MSSQL:** Enable CDC at database level, create a PowerSync user with required permissions. + + For managed providers (Neon, Railway, Render, PlanetScale, etc.), check whether replication is already enabled. Present the exact SQL to the user and ask them to confirm it is done. + +3. **Write credentials to `.env` immediately.** As soon as database details are available, write them to the project `.env` file — do not defer: + ``` + POWERSYNC_URL=https://your-instance.powersync.journeyapps.com # or http://localhost:8080 for self-hosted + PS_DATABASE_URI=postgresql://user:pass@host:5432/db + BACKEND_URL=http://localhost:3001 + ``` + Both `service.yaml` (via `!env` tags) and app code depend on these values. + +4. **Scaffold and configure PowerSync.** + - **Cloud:** `powersync init cloud` → edit `powersync/service.yaml` and `powersync/sync-config.yaml` → `powersync link cloud --create --project-id=` → `powersync deploy` + - **Self-hosted:** `powersync init self-hosted` → `powersync docker configure` → edit `powersync/service.yaml` and `powersync/sync-config.yaml` → `powersync docker start` + + See `references/powersync-cli.md` for the full CLI reference. + +5. **Generate sync config.** Load `references/sync-config.md`. Use Sync Streams (not legacy Sync Rules): + ```yaml + config: + edition: 3 + + streams: + my_data: + auto_subscribe: true + query: SELECT * FROM my_table WHERE user_id = auth.user_id() + ``` + +6. **Configure client auth.** Use custom JWT auth in `service.yaml`: + ```yaml + client_auth: + jwks_uri: !env PS_JWKS_URI + audience: + - !env POWERSYNC_URL + ``` + For self-hosted local development, add `block_local_jwks: false` if the JWKS URI resolves to a private IP. + + For development without full auth setup, use `powersync generate token --subject=user-1` after configuring at least one signing key. + +7. **Deploy config.** + - **Cloud:** `powersync deploy service-config` then `powersync deploy sync-config` + - **Self-hosted:** `powersync docker reset` (picks up config changes) + +### Phase 2: Backend API + +Only start this after the PowerSync service is configured and running. + +8. **Create the backend API.** Load `references/custom-backend.md` for full details. Your backend needs three endpoints: + + | Endpoint | Purpose | + |----------|---------| + | `GET /.well-known/jwks.json` | Serves your public keys — PowerSync fetches this to verify client JWTs | + | `GET /api/auth/token` | Generates a signed PowerSync JWT for an authenticated user | + | `POST /api/powersync/upload` | Receives writes from the client's upload queue and applies them to your database | + + Key rules for the upload endpoint: + - Apply writes synchronously (no job queues) + - Always return 2xx — even for validation errors (4xx blocks the queue permanently) + - Validate `op.table` against an allowlist to prevent SQL injection + +9. **Set up JWT signing.** Generate an RSA key pair (or ECDSA/EdDSA), implement the JWKS endpoint, and implement the token endpoint. See `references/custom-backend.md` § 2 for full code examples. + + Required JWT claims: `sub` (user ID), `aud` (must match PowerSync audience config), `iat`, `exp` (max 24h after iat), `kid` (must match JWKS). + +10. **Verify the auth chain.** Confirm: + - JWKS endpoint returns valid keys (`curl http://localhost:3001/.well-known/jwks.json`) + - Token endpoint returns a signed JWT + - PowerSync can reach the JWKS URI (use `host.docker.internal` from Docker, not `localhost`) + +### Phase 3: Backend Readiness Gate + +Do not proceed to app code until all items are verified: + +- [ ] PowerSync instance exists and is running +- [ ] Source database connection is configured +- [ ] Source database replication/publication/CDC is set up +- [ ] Sync config is deployed with `config: edition: 3` +- [ ] Client auth is configured (JWKS URI or inline keys) +- [ ] Backend API is running (JWKS + token + upload endpoints) +- [ ] All credentials and URLs are in `.env` + +If any item is missing, finish it before writing app code. + +### Phase 4: App Integration + +Only after Phase 3 is complete. + +11. **Install SDK packages.** Load the appropriate SDK reference file for your framework: + - JS/TS base: `references/sdks/powersync-js.md` + - Then the framework file: `references/sdks/powersync-js-react.md`, `powersync-js-vue.md`, etc. + +12. **Define the client schema.** Generate it from the deployed sync config: + ```bash + powersync generate schema --output=ts --output-path=./src/schema.ts + ``` + Or write it manually — but never define the `id` column (it is automatic). + +13. **Implement the backend connector.** Create `fetchCredentials()` (calls your token endpoint) and `uploadData()` (calls your upload endpoint). See `references/custom-backend.md` § 4 for full code. + + Critical: `transaction.complete()` is mandatory in `uploadData` — without it the queue stalls permanently. + +14. **Initialize PowerSync and connect.** + - `connect()` is fire-and-forget — use `waitForFirstSync()` if you need readiness before rendering. + - Use `disconnectAndClear()` on logout or user switch. + +15. **Switch reads to local SQLite** and test offline behavior. + +## If the App Is Stuck on `Syncing...` + +Check these in order — do not assume the bug is in frontend code: + +1. PowerSync endpoint URL in `fetchCredentials()` is correct (not the backend URL) +2. Source DB connection is configured in the PowerSync service +3. Sync config is deployed with `config: edition: 3` +4. Client auth is configured and JWKS is reachable from the PowerSync service +5. Source database replication/publication/CDC is set up for the synced tables +6. Token endpoint returns a valid JWT with correct `sub`, `aud`, `kid`, and `exp` + +Only inspect frontend code after all six checks pass. Load `references/powersync-debug.md` for advanced diagnostics. + +## Minimum `service.yaml` Examples + +### Cloud + Custom Auth + +```yaml +replication: + connections: + - type: postgresql + uri: !env PS_DATABASE_URI + +client_auth: + jwks_uri: !env PS_JWKS_URI + audience: + - !env POWERSYNC_URL +``` + +### Self-Hosted + Custom Auth + +```yaml +replication: + connections: + - type: postgresql + uri: !env PS_DATA_SOURCE_URI + +storage: + type: mongodb + uri: !env PS_STORAGE_URI + +client_auth: + jwks_uri: !env PS_JWKS_URI + audience: + - !env POWERSYNC_URL + block_local_jwks: false # For local development only + +api: + tokens: + - !env PS_ADMIN_TOKEN +``` + +## Common Pitfalls + +1. **4xx from upload endpoint** — Blocks the upload queue permanently. Always return 2xx. +2. **Async write processing** — PowerSync expects writes reflected in the database immediately for checkpoint consistency. Do not queue writes. +3. **Missing REPLICA IDENTITY FULL** — DELETE operations won't sync to clients without it. +4. **Token `exp - iat > 86400`** — PowerSync rejects tokens with expiry > 24h. +5. **`kid` mismatch** — The JWT header `kid` must match a key in your JWKS. Causes `PSYNC_S2101`. +6. **`block_local_jwks` not set** — JWKS URIs resolving to private IPs are blocked by default. Set `block_local_jwks: false` for local development. +7. **Wrong `endpoint` in `fetchCredentials()`** — Must be the PowerSync URL, not your backend URL. Causes 404 on `/sync/stream`. diff --git a/skills/powersync/references/powersync-service.md b/skills/powersync/references/powersync-service.md index 8b7985f..f8096e6 100644 --- a/skills/powersync/references/powersync-service.md +++ b/skills/powersync/references/powersync-service.md @@ -104,12 +104,14 @@ api: - !env PS_ADMIN_TOKEN ``` -### Minimal Cloud service.yaml Example +### Minimal Cloud service.yaml Examples -For PowerSync Cloud, the minimal shape is: +For PowerSync Cloud, the minimal shape depends on your auth provider. + +**Cloud + Supabase Auth:** ```yaml -# powersync/service.yaml — Cloud +# powersync/service.yaml — Cloud with Supabase replication: connections: - type: postgresql @@ -119,7 +121,22 @@ client_auth: supabase: true ``` -If you are using Cloud with Supabase, this is the easiest example to reason about. Do not mentally translate from the self-hosted example first. +**Cloud + Custom Auth (JWKS):** + +```yaml +# powersync/service.yaml — Cloud with custom JWT auth +replication: + connections: + - type: postgresql + uri: !env PS_DATABASE_URI + +client_auth: + jwks_uri: !env PS_JWKS_URI + audience: + - !env POWERSYNC_URL +``` + +Choose the example that matches your auth provider. See `references/supabase-auth.md` for Supabase details or `references/custom-backend.md` for custom JWT setup. ### Replication connections diff --git a/skills/powersync/references/sync-config.md b/skills/powersync/references/sync-config.md index 39d445b..4fcc17f 100644 --- a/skills/powersync/references/sync-config.md +++ b/skills/powersync/references/sync-config.md @@ -56,7 +56,7 @@ streams: query: SELECT * FROM my_table WHERE user_id = auth.user_id() ``` -### Minimal Supabase-Oriented Example +### Minimal Example ```yaml config: From dd2c1c6dd30113d27e6bda53e5378d694f2b3131 Mon Sep 17 00:00:00 2001 From: bean1352 Date: Tue, 24 Mar 2026 14:34:15 +0200 Subject: [PATCH 5/9] Rework onboarding documentation for PowerSync: consolidate custom backend references, update Supabase onboarding paths, and improve service setup instructions. Remove outdated files --- skills/powersync/AGENTS.md | 37 +-- skills/powersync/SKILL.md | 8 +- skills/powersync/references/custom-backend.md | 226 ++---------------- .../references/onboarding-custom-web.md | 208 ---------------- .../powersync/references/onboarding-custom.md | 111 +++++++++ .../references/onboarding-supabase-web.md | 223 ----------------- .../references/onboarding-supabase.md | 94 ++++++++ .../powersync/references/powersync-debug.md | 8 +- 8 files changed, 250 insertions(+), 665 deletions(-) delete mode 100644 skills/powersync/references/onboarding-custom-web.md create mode 100644 skills/powersync/references/onboarding-custom.md delete mode 100644 skills/powersync/references/onboarding-supabase-web.md create mode 100644 skills/powersync/references/onboarding-supabase.md diff --git a/skills/powersync/AGENTS.md b/skills/powersync/AGENTS.md index 6849491..c783a7a 100644 --- a/skills/powersync/AGENTS.md +++ b/skills/powersync/AGENTS.md @@ -39,11 +39,11 @@ When the task is to add PowerSync to an app, follow this sequence in order: 1. Identify the platform: **Cloud** or **self-hosted**. 2. **Identify the backend.** If the user has not specified a backend, **ask them** which database/backend they want to use (e.g. Supabase, custom Postgres, MongoDB, MySQL, MSSQL). Do not assume Supabase. The choice determines which references to load: - - **Supabase** → load `references/onboarding-supabase-web.md` + `references/supabase-auth.md` - - **Any other backend (web)** → load `references/onboarding-custom-web.md` + `references/custom-backend.md` — the agent must create a backend API with an `uploadData`, `token` endpoint and a JWT auth provider. Do not skip this. + - **Supabase** → load `references/onboarding-supabase.md` + - **Any other backend** → load `references/onboarding-custom.md` — the agent must create a backend API with `uploadData`, token, and JWKS endpoints. Do not skip this. 3. If the backend is Supabase and it is unclear whether the user means **online (Supabase Cloud)** or **locally hosted** (e.g. `supabase start`), **ask the user** before choosing connection strings, auth config, or references. 4. Collect required inputs before coding. -5. **Always load `references/sync-config.md`** and generate sync config. Also set up any required source database configuration (e.g. Supabase publication SQL, Postgres publication, MongoDB replica set). Sync config is mandatory for every PowerSync project — without it, nothing syncs. +5. **Always load `references/sync-config.md`** and generate sync config. For source database setup (publication SQL, replication, CDC), see `references/powersync-service.md` § "Source Database Setup". Sync config is mandatory for every PowerSync project — without it, nothing syncs. 6. **Persist all credentials and connection details to `.env` immediately.** When a CLI or dashboard provides database credentials (host, port, database name, username, password, connection URI), write them to the project's `.env` file right away — before deploying config or writing app code. Both `service.yaml` (via `!env` tags) and app code (e.g. `fetchCredentials`) depend on these values. If they are not in `.env`, the PowerSync config will deploy with broken connection details and the app will not connect. Include at minimum: `POWERSYNC_URL`, the Postgres connection URI (e.g. `PS_DATABASE_URI`), and any backend-specific keys. 7. **Create/link the instance and deploy config before writing app code.** Use the CLI — do not create config files manually. For Cloud: `powersync init cloud` → edit config → `powersync link cloud --create` → `powersync deploy`. For self-hosted: `powersync init self-hosted` → `powersync docker configure` → `powersync docker start`. For source database setup the agent cannot run (e.g. Supabase publication SQL), present the exact SQL and ask the user to confirm it is done. The app will not sync without deployed config. 8. Only after backend readiness is confirmed, implement app-side PowerSync integration. @@ -61,7 +61,8 @@ Additional footguns are in their reference files — do not load these unless wo - **Config/CLI:** `references/powersync-cli.md`, `references/powersync-service.md`, `references/sync-config.md` - **JS/TS SDK:** `references/sdks/powersync-js.md` (type-only imports, connect() semantics, transaction.complete()) - **React:** `references/sdks/powersync-js-react.md` (Strict Mode, Suspense, Next.js) -- **Supabase:** `references/onboarding-supabase-web.md` (CLI limitations, publication SQL) +- **Supabase:** `references/supabase-auth.md` (JWT signing keys, publication SQL, local Supabase) +- **Custom backend:** `references/custom-backend.md` (upload endpoint rules, JWT pitfalls) ## Required Inputs Before Coding @@ -105,22 +106,7 @@ Use the CLI to verify and complete any missing items. For steps the agent cannot ## First Response for `Syncing...` -When the app shows `Syncing...`, check these in order before asking for browser console logs: - -1. Confirm the PowerSync endpoint URL returned by `fetchCredentials()`. -2. Confirm the PowerSync service has a valid source DB connection. -3. Confirm sync config is deployed and uses `config: edition: 3`. -4. Confirm client auth is configured correctly. -5. Confirm the source database publication/replication is set up for the synced tables. -6. Only then inspect frontend connector or SDK state. - -Before requesting console logs, ask the user to confirm: - -- whether the instance exists -- whether database connection is configured -- whether sync config was deployed -- whether client auth was enabled -- whether the source database publication/replication SQL was run +Follow `references/powersync-debug.md` § "First Response When the UI Is Stuck on `Syncing...`" — verify backend readiness (endpoint URL, DB connection, sync config, client auth, replication/publication) before inspecting frontend code or requesting console logs. ## Setup Paths @@ -200,14 +186,13 @@ Key rule: **client writes never go through PowerSync**. They go from the app's u | Task | Start with | Load on demand | |------|-----------|----------------| -| React web + Supabase + Cloud | `references/onboarding-supabase-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/supabase-auth.md`, `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` (when writing app code) | -| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` + `references/sync-config.md` | SDK files for your platform (when writing app code) | -| PowerSync + Supabase | `references/onboarding-supabase-web.md` + `references/sync-config.md` | `references/supabase-auth.md` (when configuring auth) | -| Debugging sync issues | `references/powersync-debug.md` | — | +| Supabase + PowerSync | `references/onboarding-supabase.md` | `references/supabase-auth.md`, `references/sync-config.md`, SDK files (when writing app code) | +| Custom backend (non-Supabase) | `references/onboarding-custom.md` | `references/custom-backend.md`, `references/sync-config.md`, SDK files (when writing app code) | +| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` | `references/sync-config.md`, SDK files (when writing app code) | +| Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` | `references/sync-config.md` | | Writing sync config | `references/sync-config.md` | — | -| Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` + `references/sync-config.md` | — | +| Debugging sync issues | `references/powersync-debug.md` | — | | Attachments | `references/attachments.md` | — | -| Web + custom backend (non-Supabase) | `references/onboarding-custom-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/custom-backend.md`, `references/powersync-service.md`, SDK files (when writing app code) | | Architecture overview | This file is sufficient | `references/powersync-overview.md` for deep links | ## SDK Reference Files diff --git a/skills/powersync/SKILL.md b/skills/powersync/SKILL.md index 49e2070..a2eb34e 100644 --- a/skills/powersync/SKILL.md +++ b/skills/powersync/SKILL.md @@ -28,10 +28,10 @@ Use this skill to onboard a project onto PowerSync without trial-and-error. Trea | Task | Start with | Load on demand | |------|-----------|----------------| -| New project setup | `references/powersync-cli.md` + `references/sync-config.md` | SDK files for your platform (when writing app code) | -| React web + Supabase + Cloud | `references/onboarding-supabase-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/supabase-auth.md`, `references/sdks/powersync-js.md` + `references/sdks/powersync-js-react.md` | -| Web + custom backend (non-Supabase) | `references/onboarding-custom-web.md` + `references/powersync-cli.md` + `references/sync-config.md` | `references/custom-backend.md`, `references/powersync-service.md`, SDK files | -| Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` + `references/sync-config.md` | — | +| Supabase + PowerSync | `references/onboarding-supabase.md` | `references/supabase-auth.md`, `references/sync-config.md`, SDK files | +| Custom backend (non-Supabase) | `references/onboarding-custom.md` | `references/custom-backend.md`, `references/sync-config.md`, SDK files | +| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` | `references/sync-config.md`, SDK files | +| Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` | `references/sync-config.md` | | Writing sync config | `references/sync-config.md` | — | | Debugging sync issues | `references/powersync-debug.md` | — | | Attachments | `references/attachments.md` | — | diff --git a/skills/powersync/references/custom-backend.md b/skills/powersync/references/custom-backend.md index 6534a8e..7a4c9f3 100644 --- a/skills/powersync/references/custom-backend.md +++ b/skills/powersync/references/custom-backend.md @@ -1,13 +1,17 @@ --- name: custom-backend -description: Building a custom backend for PowerSync — server-side API for uploadData, custom JWT auth, Postgres setup without Supabase, and end-to-end connector examples +description: Building a custom backend for PowerSync — server-side API for uploadData, custom JWT auth, JWKS endpoints, and client-side connector implementation metadata: - tags: backend, custom, jwt, auth, express, fastify, postgres, uploadData, api, non-supabase + tags: backend, custom, jwt, auth, express, fastify, uploadData, api, non-supabase --- # Custom Backend for PowerSync -Use this file when building a PowerSync integration **without Supabase** — a custom Postgres database, your own auth, and a backend API (Express, Fastify, Hono, etc.) that receives writes from the client's upload queue. +Use this file when building a PowerSync integration **without Supabase** — your own auth and a backend API that receives writes from the client's upload queue. + +For **source database setup** (Postgres replication, MongoDB replica set, MySQL binlog, MSSQL CDC), see `references/powersync-service.md` § "Source Database Setup". + +For **service.yaml configuration** (Cloud or self-hosted templates), see `references/powersync-service.md`. | Resource | Description | |----------|-------------| @@ -20,11 +24,10 @@ Use this file when building a PowerSync integration **without Supabase** — a c ## Architecture Recap ``` -Client App Your Backend API Postgres DB +Client App Your Backend API Source Database | | | |-- uploadData() POST ---------->|--- INSERT/UPDATE/DELETE -->| | |<-- 2xx response -----------| - | | | | | PowerSync Service <-------------- CDC / logical replication ---| | @@ -33,63 +36,7 @@ PowerSync Service <-------------- CDC / logical replication ---| Key rule: **client writes never go through PowerSync**. The upload queue sends writes to YOUR backend API. PowerSync only handles the read/sync path. -## 1. Postgres Database Setup - -Skip this section if using Supabase — see `references/supabase-auth.md` instead. - -### Enable Logical Replication - -```sql --- Check current setting -SHOW wal_level; --- If not 'logical', set it (requires restart): -ALTER SYSTEM SET wal_level = 'logical'; --- Restart PostgreSQL after this change -``` - -Most managed Postgres providers (Neon, Railway, Render, etc.) have logical replication enabled or expose a setting for it. Check your provider's docs. - -### Create Replication User - -Generate a secure password — never use placeholder values. - -```sql --- Create dedicated user for PowerSync replication -CREATE USER powersync_replication WITH REPLICATION PASSWORD 'YOUR_GENERATED_PASSWORD'; - --- Grant read access to synced tables -GRANT SELECT ON ALL TABLES IN SCHEMA public TO powersync_replication; -ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO powersync_replication; -``` - -### Create Publication - -```sql --- List specific tables (recommended) -CREATE PUBLICATION powersync FOR TABLE users, posts, comments; - --- Or all tables (simpler but less precise) -CREATE PUBLICATION powersync FOR ALL TABLES; - --- For each synced table, set REPLICA IDENTITY FULL (required for DELETE sync) -ALTER TABLE users REPLICA IDENTITY FULL; -ALTER TABLE posts REPLICA IDENTITY FULL; -ALTER TABLE comments REPLICA IDENTITY FULL; -``` - -### Service Config Connection - -```yaml -# powersync/service.yaml -replication: - connections: - - type: postgresql - uri: !env PS_DATA_SOURCE_URI - # For local Postgres without SSL: - # sslmode: disable -``` - -## 2. Custom JWT Auth +## 1. Custom JWT Auth PowerSync verifies JWTs from client apps. Without Supabase, you must generate and serve your own JWTs and JWKS. @@ -156,7 +103,7 @@ export async function getJWKS() { ### Generate JWTs (Token Endpoint) -The token endpoint generates a PowerSync JWT for an already-authenticated user. It does **not** handle user login — your app authenticates users separately via sessions, OAuth, or whatever mechanism you use. The token endpoint simply takes a `user_id` and returns a signed JWT. +The token endpoint generates a PowerSync JWT for an already-authenticated user. It does **not** handle user login — your app authenticates users separately via sessions, OAuth, or whatever mechanism you use. ```ts import { SignJWT, importPKCS8 } from 'jose'; @@ -180,42 +127,15 @@ export async function generateToken(userId: string): Promise { } ``` -### PowerSync Service Config for Custom Auth - -```yaml -# powersync/service.yaml -client_auth: - # Option 1: JWKS URI (recommended — supports key rotation) - jwks_uri: https://your-backend.com/.well-known/jwks.json - audience: - - powersync - - # Option 2: Inline JWKS (no external endpoint needed) - # jwks: - # keys: - # - kty: RSA - # n: "..." - # e: "AQAB" - # alg: RS256 - # kid: powersync-key-1 - # use: sig - # audience: - # - powersync -``` +### Service Config for Custom Auth -For local development with `host.docker.internal`: +See `references/powersync-service.md` § "Minimal Cloud service.yaml Examples" for the Cloud + Custom Auth template, or § "Complete service.yaml Example" for self-hosted. -```yaml -client_auth: - jwks_uri: http://host.docker.internal:3001/.well-known/jwks.json - audience: - - powersync - block_local_jwks: false # Required when JWKS URI resolves to a private IP -``` +For local development with `host.docker.internal`, set `block_local_jwks: false` in service config when the JWKS URI resolves to a private IP. -### Development Tokens (Self-Hosted) +### Development Tokens -For quick development without setting up full auth, configure a signing key in `service.yaml` and use the CLI to generate tokens: +For quick development without full auth, configure a signing key in `service.yaml` and use the CLI: ```bash powersync generate token --user-id "test-user-123" @@ -233,12 +153,12 @@ When using a JWKS URI: 4. Wait for all old tokens to expire (up to their `exp`). 5. Remove the old key from the JWKS endpoint. -## 3. Backend API for uploadData +## 2. Backend API for uploadData The client's `uploadData()` sends pending writes to your backend API. Your backend must: 1. Accept the write operations. -2. Apply them to Postgres **synchronously** (do not queue for later processing). +2. Apply them to the database **synchronously** (do not queue for later processing). 3. Return 2xx — even for validation errors. ### Request/Response Contract @@ -280,15 +200,10 @@ app.use(express.json()); const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }); // Token endpoint — client calls this in fetchCredentials() -// This does NOT handle user authentication — your app authenticates users -// separately (session, OAuth, etc.). This endpoint just generates a -// PowerSync JWT for an already-authenticated user. app.get('/api/auth/token', async (req, res) => { const userId = req.query.user_id as string; if (!userId) return res.status(400).json({ error: 'user_id is required' }); - // Optional: verify the caller is actually this user (check session, etc.) - const token = await generateToken(userId); res.json({ token, @@ -360,7 +275,7 @@ app.post('/api/powersync/upload', async (req, res) => { app.listen(3001, () => console.log('Backend running on :3001')); ``` -**IMPORTANT:** The upload endpoint example above uses string interpolation for table names. In production, validate `op.table` against an allowlist of known tables to prevent SQL injection: +**IMPORTANT:** The upload endpoint example above uses string interpolation for table names. In production, validate `op.table` against an allowlist: ```ts const ALLOWED_TABLES = new Set(['posts', 'comments', 'users']); @@ -371,16 +286,15 @@ if (!ALLOWED_TABLES.has(op.table)) { ### Boolean Conversion -PowerSync stores booleans as integers (0/1) in SQLite. If your Postgres schema uses native `boolean` columns, convert before writing: +PowerSync stores booleans as integers (0/1) in SQLite. If your database uses native `boolean` columns, convert before writing: ```ts -// Convert 0/1 to true/false for boolean columns if (op.table === 'posts' && op.opData?.is_published !== undefined) { op.opData.is_published = Boolean(op.opData.is_published); } ``` -## 4. Client-Side Connector (Custom Backend) +## 3. Client-Side Connector (Custom Backend) ### fetchCredentials @@ -399,9 +313,6 @@ export class CustomConnector implements PowerSyncBackendConnector { } async fetchCredentials(): Promise { - // Request a PowerSync JWT for the current user. - // Your app handles user authentication separately — this endpoint - // only generates PowerSync tokens for already-authenticated users. const res = await fetch( `${BACKEND_URL}/api/auth/token?user_id=${encodeURIComponent(this.userId)}` ); @@ -445,108 +356,23 @@ export class CustomConnector implements PowerSyncBackendConnector { const result = await res.json(); if (!result.success) { - // Log validation errors but still complete the transaction - // to prevent queue from stalling console.warn('Upload had errors:', result.error); } // MUST call complete() — without this the queue stalls permanently await transaction.complete(); } catch (ex) { - // Throw to retry later — PowerSync backs off automatically throw ex; } } } ``` -## 5. Complete Self-Hosted Docker Compose - -A full local development setup with custom Postgres, MongoDB (for PowerSync bucket storage), and the PowerSync service: - -```yaml -services: - postgres: - image: postgres:16 - ports: - - "5432:5432" - environment: - POSTGRES_USER: app - POSTGRES_PASSWORD: app_password - POSTGRES_DB: myapp - command: ["postgres", "-c", "wal_level=logical"] - volumes: - - pg-data:/var/lib/postgresql/data - - mongo: - image: mongo:7 - command: mongod --replSet rs0 - ports: - - "27017:27017" - volumes: - - mongo-data:/data/db - - mongo-init: - image: mongo:7 - depends_on: - - mongo - restart: "no" - entrypoint: > - mongosh --host mongo --eval " - try { rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'mongo:27017' }] }); } - catch(e) { if (e.codeName !== 'AlreadyInitialized') throw e; } - " - - powersync: - image: journeyapps/powersync-service:latest - depends_on: - - mongo - - mongo-init - ports: - - "8080:8080" - environment: - PS_DATA_SOURCE_URI: "postgresql://app:app_password@postgres:5432/myapp" - PS_STORAGE_URI: "mongodb://mongo:27017/powersync_storage?replicaSet=rs0" - PS_JWKS_URI: "http://host.docker.internal:3001/.well-known/jwks.json" - PS_ADMIN_TOKEN: "dev-admin-token" - POWERSYNC_SYNC_CONFIG_B64: "" - volumes: - - ./powersync/service.yaml:/config/service.yaml - command: ["start", "-c", "/config/service.yaml"] - -volumes: - pg-data: - mongo-data: -``` - -Notes: -- `PS_JWKS_URI` uses `host.docker.internal` to reach your backend API running on the host machine. -- If Postgres is also in Docker (same compose), use the service name (`postgres:5432`) — no `host.docker.internal` needed, and no `sslmode: disable` needed. -- If Postgres is on the host (e.g. local install, Supabase), use `host.docker.internal:` and add `sslmode: disable` in the service.yaml if SSL is not configured. -- Generate the base64 sync config: `base64 -i ./powersync/sync-config.yaml` (macOS) or `base64 -w0 ./powersync/sync-config.yaml` (Linux). - ## Common Pitfalls -### 1. Returning 4xx from upload endpoint - -A 4xx response blocks the upload queue **permanently**. Always return 2xx, even for validation errors. Surface errors through a synced table or response body, never through HTTP status codes. - -### 2. Async processing of writes - -Do not accept the write and process it later (e.g., via a job queue). PowerSync's checkpoint system expects writes to be reflected in Postgres immediately so they can sync back to clients. If writes are delayed, clients see stale data or missing rows. - -### 3. Missing REPLICA IDENTITY FULL - -Without `ALTER TABLE ... REPLICA IDENTITY FULL`, PowerSync cannot sync DELETE operations to clients. The delete will apply in Postgres but clients keep the deleted row in local SQLite. - -### 4. Token expiry exceeding 24 hours - -PowerSync rejects tokens with `exp - iat > 86400` (24 hours). Use 1 hour for production, up to 24 hours for development. - -### 5. Missing `kid` in JWT header - -If the `kid` (Key ID) in the JWT header does not match any key in the JWKS, PowerSync returns `PSYNC_S2101: Could not find an appropriate key in the keystore`. Ensure the `kid` used in `setProtectedHeader()` matches the `kid` in your JWKS endpoint. - -### 6. Forgetting `block_local_jwks: false` for local development - -When the JWKS URI resolves to a private IP (`host.docker.internal`, `localhost`, `127.0.0.1`), PowerSync blocks the request by default. Set `block_local_jwks: false` in your service config for local development. +1. **4xx from upload endpoint** — Blocks the upload queue **permanently**. Always return 2xx, even for validation errors. +2. **Async processing of writes** — PowerSync expects writes reflected in the database immediately. Do not queue writes. +3. **Token expiry > 24h** — PowerSync rejects tokens with `exp - iat > 86400`. Use short-lived tokens (1h production, max 24h dev). +4. **`kid` mismatch** — JWT header `kid` must match a key in your JWKS. Causes `PSYNC_S2101`. +5. **`block_local_jwks` not set** — JWKS URIs resolving to private IPs are blocked by default. Set `block_local_jwks: false` for local dev. +6. **Wrong `endpoint` in `fetchCredentials()`** — Must be the PowerSync URL, not your backend URL. Causes 404 on `/sync/stream`. diff --git a/skills/powersync/references/onboarding-custom-web.md b/skills/powersync/references/onboarding-custom-web.md deleted file mode 100644 index 0ef6219..0000000 --- a/skills/powersync/references/onboarding-custom-web.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -name: onboarding-custom-web -description: Step-by-step onboarding recipe for a web app using a custom backend (non-Supabase) with PowerSync — custom Postgres, custom JWT auth, backend API for uploadData, and client integration -metadata: - tags: onboarding, web, custom, backend, postgres, jwt, auth, uploadData, recipe, cloud, self-hosted ---- - -# Web App + Custom Backend + PowerSync - -Use this file when onboarding a web app onto PowerSync with a **non-Supabase backend** — your own Postgres (or other supported database), your own auth, and your own backend API. This recipe works for both PowerSync Cloud and self-hosted. - -**Strongly prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md) as the first option** for setup. See `references/powersync-cli.md`. Fall back to the dashboard (Cloud) or manual Docker config (self-hosted) only if the CLI is unavailable or the user explicitly prefers it. - -## Required Inputs - -Collect these before writing any code: - -- **Cloud or self-hosted** — which PowerSync hosting model -- **Database type and connection details** — Postgres, MongoDB, MySQL, or MSSQL (host, port, database, username, password or connection URI) -- Whether a PowerSync instance already exists -- PowerSync instance URL, if the instance already exists -- Project ID and instance ID, if using CLI with an existing Cloud instance -- How the user wants to handle auth (custom JWT with own keys, third-party auth provider like Auth0/Firebase, or dev tokens for prototyping) -- Whether they have an existing backend API or need to create one - -Only ask for secrets (database password, private keys) when you are at the step that actually needs them. - -## Workflow - -Follow this sequence exactly. **Do not skip ahead to app code.** - -### Phase 1: Service Setup - -1. **Confirm the path.** Verify: PowerSync (Cloud or self-hosted) + custom backend + web app. - -2. **Set up the source database.** Based on the database type, load `references/powersync-service.md` for the relevant quick start: - - **Postgres:** Enable logical replication (`wal_level = logical`), create a replication user, create a publication for the synced tables, set `REPLICA IDENTITY FULL` on each synced table. - - **MongoDB:** Ensure replica set is initialized, create a user with read access + write access to `_powersync_checkpoints`. - - **MySQL:** Enable binary logging with `ROW` format and GTIDs, create a replication user. - - **MSSQL:** Enable CDC at database level, create a PowerSync user with required permissions. - - For managed providers (Neon, Railway, Render, PlanetScale, etc.), check whether replication is already enabled. Present the exact SQL to the user and ask them to confirm it is done. - -3. **Write credentials to `.env` immediately.** As soon as database details are available, write them to the project `.env` file — do not defer: - ``` - POWERSYNC_URL=https://your-instance.powersync.journeyapps.com # or http://localhost:8080 for self-hosted - PS_DATABASE_URI=postgresql://user:pass@host:5432/db - BACKEND_URL=http://localhost:3001 - ``` - Both `service.yaml` (via `!env` tags) and app code depend on these values. - -4. **Scaffold and configure PowerSync.** - - **Cloud:** `powersync init cloud` → edit `powersync/service.yaml` and `powersync/sync-config.yaml` → `powersync link cloud --create --project-id=` → `powersync deploy` - - **Self-hosted:** `powersync init self-hosted` → `powersync docker configure` → edit `powersync/service.yaml` and `powersync/sync-config.yaml` → `powersync docker start` - - See `references/powersync-cli.md` for the full CLI reference. - -5. **Generate sync config.** Load `references/sync-config.md`. Use Sync Streams (not legacy Sync Rules): - ```yaml - config: - edition: 3 - - streams: - my_data: - auto_subscribe: true - query: SELECT * FROM my_table WHERE user_id = auth.user_id() - ``` - -6. **Configure client auth.** Use custom JWT auth in `service.yaml`: - ```yaml - client_auth: - jwks_uri: !env PS_JWKS_URI - audience: - - !env POWERSYNC_URL - ``` - For self-hosted local development, add `block_local_jwks: false` if the JWKS URI resolves to a private IP. - - For development without full auth setup, use `powersync generate token --subject=user-1` after configuring at least one signing key. - -7. **Deploy config.** - - **Cloud:** `powersync deploy service-config` then `powersync deploy sync-config` - - **Self-hosted:** `powersync docker reset` (picks up config changes) - -### Phase 2: Backend API - -Only start this after the PowerSync service is configured and running. - -8. **Create the backend API.** Load `references/custom-backend.md` for full details. Your backend needs three endpoints: - - | Endpoint | Purpose | - |----------|---------| - | `GET /.well-known/jwks.json` | Serves your public keys — PowerSync fetches this to verify client JWTs | - | `GET /api/auth/token` | Generates a signed PowerSync JWT for an authenticated user | - | `POST /api/powersync/upload` | Receives writes from the client's upload queue and applies them to your database | - - Key rules for the upload endpoint: - - Apply writes synchronously (no job queues) - - Always return 2xx — even for validation errors (4xx blocks the queue permanently) - - Validate `op.table` against an allowlist to prevent SQL injection - -9. **Set up JWT signing.** Generate an RSA key pair (or ECDSA/EdDSA), implement the JWKS endpoint, and implement the token endpoint. See `references/custom-backend.md` § 2 for full code examples. - - Required JWT claims: `sub` (user ID), `aud` (must match PowerSync audience config), `iat`, `exp` (max 24h after iat), `kid` (must match JWKS). - -10. **Verify the auth chain.** Confirm: - - JWKS endpoint returns valid keys (`curl http://localhost:3001/.well-known/jwks.json`) - - Token endpoint returns a signed JWT - - PowerSync can reach the JWKS URI (use `host.docker.internal` from Docker, not `localhost`) - -### Phase 3: Backend Readiness Gate - -Do not proceed to app code until all items are verified: - -- [ ] PowerSync instance exists and is running -- [ ] Source database connection is configured -- [ ] Source database replication/publication/CDC is set up -- [ ] Sync config is deployed with `config: edition: 3` -- [ ] Client auth is configured (JWKS URI or inline keys) -- [ ] Backend API is running (JWKS + token + upload endpoints) -- [ ] All credentials and URLs are in `.env` - -If any item is missing, finish it before writing app code. - -### Phase 4: App Integration - -Only after Phase 3 is complete. - -11. **Install SDK packages.** Load the appropriate SDK reference file for your framework: - - JS/TS base: `references/sdks/powersync-js.md` - - Then the framework file: `references/sdks/powersync-js-react.md`, `powersync-js-vue.md`, etc. - -12. **Define the client schema.** Generate it from the deployed sync config: - ```bash - powersync generate schema --output=ts --output-path=./src/schema.ts - ``` - Or write it manually — but never define the `id` column (it is automatic). - -13. **Implement the backend connector.** Create `fetchCredentials()` (calls your token endpoint) and `uploadData()` (calls your upload endpoint). See `references/custom-backend.md` § 4 for full code. - - Critical: `transaction.complete()` is mandatory in `uploadData` — without it the queue stalls permanently. - -14. **Initialize PowerSync and connect.** - - `connect()` is fire-and-forget — use `waitForFirstSync()` if you need readiness before rendering. - - Use `disconnectAndClear()` on logout or user switch. - -15. **Switch reads to local SQLite** and test offline behavior. - -## If the App Is Stuck on `Syncing...` - -Check these in order — do not assume the bug is in frontend code: - -1. PowerSync endpoint URL in `fetchCredentials()` is correct (not the backend URL) -2. Source DB connection is configured in the PowerSync service -3. Sync config is deployed with `config: edition: 3` -4. Client auth is configured and JWKS is reachable from the PowerSync service -5. Source database replication/publication/CDC is set up for the synced tables -6. Token endpoint returns a valid JWT with correct `sub`, `aud`, `kid`, and `exp` - -Only inspect frontend code after all six checks pass. Load `references/powersync-debug.md` for advanced diagnostics. - -## Minimum `service.yaml` Examples - -### Cloud + Custom Auth - -```yaml -replication: - connections: - - type: postgresql - uri: !env PS_DATABASE_URI - -client_auth: - jwks_uri: !env PS_JWKS_URI - audience: - - !env POWERSYNC_URL -``` - -### Self-Hosted + Custom Auth - -```yaml -replication: - connections: - - type: postgresql - uri: !env PS_DATA_SOURCE_URI - -storage: - type: mongodb - uri: !env PS_STORAGE_URI - -client_auth: - jwks_uri: !env PS_JWKS_URI - audience: - - !env POWERSYNC_URL - block_local_jwks: false # For local development only - -api: - tokens: - - !env PS_ADMIN_TOKEN -``` - -## Common Pitfalls - -1. **4xx from upload endpoint** — Blocks the upload queue permanently. Always return 2xx. -2. **Async write processing** — PowerSync expects writes reflected in the database immediately for checkpoint consistency. Do not queue writes. -3. **Missing REPLICA IDENTITY FULL** — DELETE operations won't sync to clients without it. -4. **Token `exp - iat > 86400`** — PowerSync rejects tokens with expiry > 24h. -5. **`kid` mismatch** — The JWT header `kid` must match a key in your JWKS. Causes `PSYNC_S2101`. -6. **`block_local_jwks` not set** — JWKS URIs resolving to private IPs are blocked by default. Set `block_local_jwks: false` for local development. -7. **Wrong `endpoint` in `fetchCredentials()`** — Must be the PowerSync URL, not your backend URL. Causes 404 on `/sync/stream`. diff --git a/skills/powersync/references/onboarding-custom.md b/skills/powersync/references/onboarding-custom.md new file mode 100644 index 0000000..b0a0153 --- /dev/null +++ b/skills/powersync/references/onboarding-custom.md @@ -0,0 +1,111 @@ +--- +name: onboarding-custom +description: Step-by-step onboarding recipe for any app using a custom backend (non-Supabase) with PowerSync — orchestrates the correct sequence and points to canonical references for each step +metadata: + tags: onboarding, custom, backend, recipe, cloud, self-hosted +--- + +# Custom Backend + PowerSync Onboarding + +Use this recipe when onboarding any app onto PowerSync with a **non-Supabase backend** — your own database, your own auth, and your own backend API. Works for all platforms (web, React Native, Flutter, Kotlin, Swift, .NET, etc.) and both Cloud and self-hosted. + +**CLI-first.** See `references/powersync-cli.md`. Fall back to the dashboard (Cloud) or manual Docker config (self-hosted) only if the CLI is unavailable or the user explicitly prefers it. + +## Required Inputs + +Collect before writing any code: + +- **Cloud or self-hosted** — which PowerSync hosting model +- **Database type** — Postgres, MongoDB, MySQL, or MSSQL +- Database connection details (host, port, database, username, password or connection URI) +- Whether a PowerSync instance already exists +- PowerSync instance URL (if instance exists) +- Project ID and instance ID (if using CLI with existing Cloud instance) +- How the user wants to handle auth (custom JWT, third-party provider like Auth0/Firebase, or dev tokens) +- Whether they have an existing backend API or need to create one + +Only ask for secrets (database password, private keys) when you are at the step that actually needs them. + +## Workflow + +Follow this sequence exactly. **Do not skip ahead to app code.** + +### Phase 1: Service Setup + +1. **Confirm the path.** Verify: PowerSync (Cloud or self-hosted) + custom backend + your platform. + +2. **Set up the source database.** Load `references/powersync-service.md` § "Source Database Setup" for the relevant quick start (Postgres, MongoDB, MySQL, or MSSQL). Present the exact SQL to the user and ask them to confirm it is done. + +3. **Write credentials to `.env` immediately.** As soon as database details are available: + ``` + POWERSYNC_URL=https://your-instance.powersync.journeyapps.com # or http://localhost:8080 for self-hosted + PS_DATABASE_URI=postgresql://user:pass@host:5432/db + BACKEND_URL=http://localhost:3001 + ``` + Both `service.yaml` (via `!env` tags) and app code depend on these values. + +4. **Scaffold and configure PowerSync.** + - **Cloud:** `powersync init cloud` → edit config → `powersync link cloud --create --project-id=` → deploy + - **Self-hosted:** `powersync init self-hosted` → `powersync docker configure` → edit config → `powersync docker start` + + See `references/powersync-cli.md` for the full CLI reference. + +5. **Configure service.yaml.** See `references/powersync-service.md` for service.yaml templates: + - Cloud + Custom Auth: § "Minimal Cloud service.yaml Examples" + - Self-hosted: § "Complete service.yaml Example" + +6. **Configure client auth.** See `references/custom-backend.md` § "Custom JWT Auth" for JWKS setup, or use `powersync generate token --subject=user-1` for dev tokens after configuring at least one signing key. + +7. **Generate sync config.** Load `references/sync-config.md`. Use Sync Streams with `config: edition: 3`. + +8. **Deploy config.** + - **Cloud:** `powersync deploy service-config` then `powersync deploy sync-config` + - **Self-hosted:** `powersync docker reset` (picks up config changes) + +### Phase 2: Backend API + +Only start this after the PowerSync service is configured and running. + +9. **Create the backend API.** Load `references/custom-backend.md` for full details. Your backend needs three endpoints: JWKS (`/.well-known/jwks.json`), token (`/api/auth/token`), and upload (`/api/powersync/upload`). + +10. **Set up JWT signing.** See `references/custom-backend.md` § "Custom JWT Auth" for key generation, JWKS endpoint, and token endpoint code. + +11. **Verify the auth chain.** Confirm JWKS endpoint returns valid keys, token endpoint returns a signed JWT, and PowerSync can reach the JWKS URI (use `host.docker.internal` from Docker, not `localhost`). + +### Phase 3: Backend Readiness Gate + +Do not proceed to app code until all items are verified: + +- [ ] PowerSync instance exists and is running +- [ ] Source database connection is configured +- [ ] Source database replication/publication/CDC is set up +- [ ] Sync config is deployed with `config: edition: 3` +- [ ] Client auth is configured (JWKS URI or inline keys) +- [ ] Backend API is running (JWKS + token + upload endpoints) +- [ ] All credentials and URLs are in `.env` + +If any item is missing, finish it before writing app code. + +### Phase 4: App Integration + +Only after Phase 3 is complete. + +12. **Install SDK packages.** Load the SDK reference file for your platform — see the SDK table in `SKILL.md`. + +13. **Define the client schema.** Generate from deployed sync config: + ```bash + powersync generate schema --output=ts --output-path=./src/schema.ts + ``` + Or write manually — but never define the `id` column (it is automatic). + +14. **Implement the backend connector.** See `references/custom-backend.md` § "Client-Side Connector" for `fetchCredentials()` and `uploadData()` code. Critical: `transaction.complete()` is mandatory — without it the queue stalls permanently. + +15. **Initialize PowerSync and connect.** + - `connect()` is fire-and-forget — use `waitForFirstSync()` if you need readiness. + - Use `disconnectAndClear()` on logout or user switch. + +16. **Switch reads to local SQLite** and test offline behavior. + +## If the App Is Stuck on `Syncing...` + +See `references/powersync-debug.md` § "First Response When the UI Is Stuck on `Syncing...`" — check backend readiness before inspecting frontend code. diff --git a/skills/powersync/references/onboarding-supabase-web.md b/skills/powersync/references/onboarding-supabase-web.md deleted file mode 100644 index 937fc8d..0000000 --- a/skills/powersync/references/onboarding-supabase-web.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -name: onboarding-supabase-web -description: Golden-path onboarding recipe for a React web app using Supabase auth with PowerSync Cloud -metadata: - tags: onboarding, react, web, supabase, cloud, cli, dashboard, recipe ---- - -# React Web + Supabase + PowerSync Cloud - -Use this file for the benchmark-style onboarding path: existing web app, Supabase auth already wired, PowerSync added for offline-first reads and queued uploads. - -**Strongly prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md) as the first option** for setup — creating/linking the instance, deploying service config, and deploying sync config. See `references/powersync-cli.md`. Fall back to the dashboard if the CLI is unavailable or the user explicitly prefers it. - -## Required Inputs - -Collect these before editing app code: - -- Whether the PowerSync Cloud instance already exists -- PowerSync instance URL, if the instance already exists -- Project ID and instance ID, if using CLI with an existing instance -- Supabase Postgres connection string, if the PowerSync source DB connection is not already configured -- `PS_ADMIN_TOKEN` or willingness to run **`powersync login`** (**PowerSync Cloud** PAT only — not used for self-hosted stacks) - -Only ask for the Postgres connection string when you are at the service configuration step. - -**Note:** The Supabase CLI (`supabase init`, `supabase link`) does **not** create a new online Supabase project — it only scaffolds local config or links to an existing one. The user must create the project via the Supabase dashboard first. - -## Workflow - -Follow this sequence exactly. **Prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md)** (see `references/powersync-cli.md`) as the first option to create/link the instance and to deploy service config and sync config. Try running the CLI commands directly before sending the user to the dashboard. - -1. Confirm the path is PowerSync Cloud + Supabase + web app. -2. **Write all credentials to `.env` immediately.** As soon as Supabase project details are available (from CLI output or dashboard), write `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `PS_DATABASE_URI` (Postgres connection string), and `POWERSYNC_URL` to the project `.env` file. Both `service.yaml` (via `!env` tags) and app code depend on these values — if they are missing, config deploys will use broken placeholders and the app will not connect. -3. Generate the sync config and Supabase SQL based on the app's tables. -4. **Run the Supabase publication SQL before deploying.** The publication must exist before PowerSync connects to the database — deploying without it causes replication errors. Present the exact SQL to the user and ask them to run it in the Supabase SQL Editor and confirm when done. -5. **Deploy backend setup before writing app code:** - - Use `powersync deploy sync-config` and `powersync deploy service-config` to deploy directly via CLI. - - Do not defer deployment to a post-implementation summary — the app will not sync without a deployed sync config. -6. Verify backend readiness. -7. Only then implement app-side PowerSync integration. -8. If the UI is stuck on `Syncing...`, re-check backend readiness before touching frontend code. - -### Sync Config Deployment - -The sync config tells the PowerSync service what data to replicate to each client. It **must** be deployed before the app will sync. Strongly prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md) to deploy it (see `references/powersync-cli.md`): - -```bash -powersync deploy sync-config -``` - -For service config changes (e.g. database connection, client auth): - -```bash -powersync deploy service-config -``` - -Prefer the CLI for creating/linking the instance and for all deploys. If the CLI is not available or the user explicitly prefers the dashboard, instruct them to paste the sync config into the PowerSync dashboard Sync Config editor and deploy from there. - -For Supabase publication SQL, the agent cannot run this directly. Present the exact SQL to the user and ask them to confirm it is done before proceeding. - -## Backend Readiness Checklist - -Do not move on until all items below are true: - -- PowerSync instance exists -- Source DB connection is configured -- Sync config is deployed -- Client auth is configured for Supabase -- PowerSync instance URL is known -- Supabase publication exists for the synced tables -- All credentials and URLs are in `.env` (`POWERSYNC_URL`, `PS_DATABASE_URI`, `SUPABASE_URL`, `SUPABASE_ANON_KEY`) - -## New Cloud Instance - -### CLI path (Recommended) - -Prefer the [PowerSync CLI](https://docs.powersync.com/tools/cli.md) for every step below unless the user says otherwise. Full reference: `references/powersync-cli.md`. Prefer `PS_ADMIN_TOKEN` in autonomous or noninteractive environments. - -1. Authenticate: - ```bash - PS_ADMIN_TOKEN=your-token-here powersync fetch instances - ``` - If no token is available, use **`powersync login`** (Cloud PAT) and treat it as interactive. Skip this for self-hosted-only paths. -2. Scaffold: - ```bash - powersync init cloud - ``` -3. Edit `powersync/service.yaml` and `powersync/sync-config.yaml` using the minimum examples below. -4. **Create and link the instance** (prefer CLI): - ```bash - powersync link cloud --create --project-id= - ``` -5. **Run the Supabase publication SQL below.** The publication must exist before PowerSync connects to the database. Present the SQL to the user, ask them to run it in the Supabase SQL Editor, and confirm it is done before proceeding. -6. **Deploy service config, then sync config** (prefer CLI): - ```bash - powersync deploy service-config - powersync deploy sync-config - ``` -7. Copy the instance URL. - -### Dashboard path - -Only use if the user explicitly prefers the dashboard or the CLI is unavailable. - -1. Create a project and a new PowerSync Cloud instance in the dashboard. -2. **Run the Supabase publication SQL below.** The publication must exist before PowerSync connects to the database. -3. In the instance, connect the Supabase database. -4. In Sync Config, deploy the minimum sync config below. -5. In Client Auth, enable **Use Supabase Auth**. -6. If Supabase uses new signing keys, leave the JWT secret field empty. -7. Copy the instance URL for app `fetchCredentials()`. - -## Existing Cloud Instance - -### Hard rule - -Never run `powersync pull instance` after editing local config. If you need to pull config, do it first and back up local files before making any manual edits. - -### CLI path (Recommended) - -1. Authenticate to **PowerSync Cloud** with `PS_ADMIN_TOKEN` if available, otherwise **`powersync login`** (Cloud PAT). -2. Pull config before editing: - ```bash - powersync pull instance --project-id= --instance-id= - ``` -3. Inspect the pulled files before making changes. -4. Edit only the files that need changes. -5. Prefer targeted deploys: - ```bash - powersync deploy service-config - powersync deploy sync-config - ``` -6. Do not pull again after editing unless you first back up the local files. - -## Minimum `service.yaml` - -Use this structure for PowerSync Cloud with Supabase: - -```yaml -# powersync/service.yaml -replication: - connections: - - type: postgresql - uri: !env PS_DATABASE_URI - -client_auth: - supabase: true -``` - -Rules: - -- The database connection must be under `replication.connections`. -- Do not use a top-level `connections:` key. -- If using legacy Supabase JWT signing keys, add `supabase_jwt_secret`. -- `!env PS_DATABASE_URI` reads from the shell environment — it is **not** prompted by the CLI. Set the variable before deploying: `PS_DATABASE_URI="postgresql://..." powersync deploy service-config`. Get the Supabase Postgres URI from Supabase Dashboard → Project Settings → Database → Connection string (URI). - -## Minimum `sync-config.yaml` - -```yaml -config: - edition: 3 - -streams: - posts: - auto_subscribe: true - query: SELECT * FROM posts WHERE user_id = auth.user_id() -``` - -Rules: - -- Keep the top-level `config:` wrapper. -- Use Sync Streams for new work. -- Scope per-user data with `auth.user_id()` when appropriate. - -## Supabase SQL - -Run this in the Supabase SQL Editor after the tables exist: - -```sql -CREATE PUBLICATION powersync FOR TABLE posts; -ALTER TABLE posts REPLICA IDENTITY FULL; -``` - -If more tables should sync, add them to the publication or use `FOR ALL TABLES`. - -## Client Auth Setup - -For PowerSync Cloud + Supabase: - -1. Enable **Use Supabase Auth** in the instance Client Auth settings. -2. If Supabase uses new signing keys, leave the JWT secret empty. -3. If Supabase uses legacy signing keys, provide the Supabase JWT secret. -4. Save and deploy. - -## Verification Before App Integration - -Verify all of these before changing app code: - -1. `service.yaml` or dashboard DB settings point at the correct Supabase database. -2. Sync config is deployed and includes `config: edition: 3`. -3. Client Auth is enabled for Supabase. -4. The PowerSync instance URL is available. -5. The Supabase publication exists for the synced tables. - -Only after that should you: - -- add the SDK packages -- implement `fetchCredentials()` -- implement `uploadData` -- switch reads to local SQLite -- test offline behavior - -## If the App Is Stuck on `Syncing...` - -Check these in order: - -1. Wrong PowerSync instance URL -2. Missing source DB connection -3. Missing or invalid sync config -4. Missing Supabase client auth setup -5. Missing Supabase publication - -Do not assume the bug is in React code until all five checks pass. diff --git a/skills/powersync/references/onboarding-supabase.md b/skills/powersync/references/onboarding-supabase.md new file mode 100644 index 0000000..9af83e8 --- /dev/null +++ b/skills/powersync/references/onboarding-supabase.md @@ -0,0 +1,94 @@ +--- +name: onboarding-supabase +description: Step-by-step onboarding recipe for any app using Supabase with PowerSync Cloud — orchestrates the correct sequence and points to canonical references for each step +metadata: + tags: onboarding, supabase, cloud, recipe +--- + +# Supabase + PowerSync Cloud Onboarding + +Use this recipe when onboarding any app onto PowerSync Cloud with a Supabase backend. This works for all platforms (web, React Native, Flutter, Kotlin, Swift, .NET, etc.). + +**CLI-first.** See `references/powersync-cli.md`. Fall back to the dashboard only if the CLI is unavailable or the user explicitly prefers it. + +## Required Inputs + +Collect before editing app code: + +- Whether the PowerSync Cloud instance already exists +- PowerSync instance URL (if instance exists) +- Project ID and instance ID (if using CLI with existing instance) +- Supabase Postgres connection string (if source DB connection not yet configured) +- `PS_ADMIN_TOKEN` or willingness to run `powersync login` (Cloud PAT only) + +Only ask for the Postgres connection string when you reach the service configuration step. + +**Note:** The Supabase CLI (`supabase init`, `supabase link`) does **not** create a new online Supabase project — it only scaffolds local config or links to an existing one. + +## Workflow + +Follow this sequence exactly. **Do not skip ahead to app code.** + +### Phase 1: Service Setup + +1. **Confirm the path.** PowerSync Cloud + Supabase + your platform. + +2. **Write credentials to `.env` immediately.** As soon as Supabase project details are available, write `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `PS_DATABASE_URI`, and `POWERSYNC_URL` to `.env`. Both `service.yaml` (via `!env` tags) and app code depend on these values. + +3. **Run the Supabase publication SQL.** The publication must exist before PowerSync connects to the database. See `references/supabase-auth.md` § "Supabase Database Setup" for the exact SQL. Present it to the user and ask them to confirm. + +4. **Scaffold and configure PowerSync.** + - **New instance (CLI):** `powersync init cloud` → edit config → `powersync link cloud --create --project-id=` → deploy + - **New instance (Dashboard):** Create project/instance → connect database → deploy sync config → enable Supabase Auth + - **Existing instance (CLI):** `powersync pull instance --project-id= --instance-id=` → edit → deploy + + See `references/powersync-cli.md` for full CLI workflow. Never run `powersync pull instance` after editing local config without backing up first. + +5. **Configure service.yaml.** See `references/powersync-service.md` § "Minimal Cloud service.yaml Examples" for the Cloud + Supabase Auth template. + +6. **Configure client auth.** See `references/supabase-auth.md` for all Supabase auth options (new signing keys, legacy HS256, local Supabase, manual JWKS). + +7. **Generate sync config.** Load `references/sync-config.md`. Use Sync Streams with `config: edition: 3`. + +8. **Deploy config** (prefer CLI): + ```bash + powersync deploy service-config + powersync deploy sync-config + ``` + +### Phase 2: Backend Readiness Gate + +Do not proceed to app code until all items are verified: + +- [ ] PowerSync instance exists and is running +- [ ] Source database connection is configured +- [ ] Supabase publication exists for synced tables +- [ ] Sync config is deployed with `config: edition: 3` +- [ ] Client auth is configured for Supabase +- [ ] All credentials and URLs are in `.env` + +If any item is missing, finish it before writing app code. + +### Phase 3: App Integration + +Only after Phase 2 is complete. + +9. **Install SDK packages.** Load the SDK reference file for your platform — see the SDK table in `SKILL.md`. + +10. **Define the client schema.** Generate from deployed sync config: + ```bash + powersync generate schema --output=ts --output-path=./src/schema.ts + ``` + Or write manually — but never define the `id` column (it is automatic). + +11. **Implement the backend connector.** See `references/supabase-auth.md` § "fetchCredentials()" for the Supabase-specific implementation. For `uploadData`, Supabase users can write directly to Supabase via the client library or use Edge Functions. + +12. **Initialize PowerSync and connect.** + - `connect()` is fire-and-forget — use `waitForFirstSync()` if you need readiness. + - Use `disconnectAndClear()` on logout or user switch. + +13. **Switch reads to local SQLite** and test offline behavior. + +## If the App Is Stuck on `Syncing...` + +See `references/powersync-debug.md` § "First Response When the UI Is Stuck on `Syncing...`" — check backend readiness before inspecting frontend code. diff --git a/skills/powersync/references/powersync-debug.md b/skills/powersync/references/powersync-debug.md index 2abec49..89350d6 100644 --- a/skills/powersync/references/powersync-debug.md +++ b/skills/powersync/references/powersync-debug.md @@ -15,11 +15,11 @@ Make sure to understand the [PowerSync Architecture](references/powersync-overvi Before asking for console logs or editing app code, verify these in order: -1. The PowerSync endpoint URL returned by `fetchCredentials()` is correct. +1. The PowerSync endpoint URL returned by `fetchCredentials()` is correct (not the backend URL). 2. The PowerSync service has a valid source DB connection. 3. Sync config was deployed and starts with `config: edition: 3`. -4. Client auth is configured correctly. -5. The Supabase publication exists for the synced tables. +4. Client auth is configured correctly (Supabase auth, custom JWKS, or other provider). +5. Source database replication/publication/CDC is set up for the synced tables. Only inspect frontend connector code or SDK state after all five checks pass. @@ -29,7 +29,7 @@ Before requesting browser console logs, ask the user to confirm: - the DB connection was configured - sync config was deployed - client auth was configured -- the Supabase SQL was run +- source database replication/publication/CDC was set up ## Check `SyncStatus` / `currentStatus` Before Investigating Further From ede33e3cea959b6f1e75686004554e3d1a5993bd Mon Sep 17 00:00:00 2001 From: bean1352 Date: Wed, 25 Mar 2026 14:28:54 +0200 Subject: [PATCH 6/9] Fix skill duplication, add Supabase connector code, clarify POWERSYNC_URL --- skills/powersync/AGENTS.md | 2 +- skills/powersync/SKILL.md | 14 +++--- .../powersync/references/onboarding-custom.md | 4 +- .../references/onboarding-supabase.md | 2 +- skills/powersync/references/powersync-cli.md | 22 +++++++++ skills/powersync/references/supabase-auth.md | 47 +++++++++++++++++++ 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/skills/powersync/AGENTS.md b/skills/powersync/AGENTS.md index c783a7a..0150cd0 100644 --- a/skills/powersync/AGENTS.md +++ b/skills/powersync/AGENTS.md @@ -203,7 +203,7 @@ Always load `references/sdks/powersync-js.md` for any JS/TS project, then load t | Framework | Load when… | File | |-----------|-----------|------| -| React / Next.js | React web app or Next.js | `references/sdks/powersync-js-react.md` | +| React / Next.js | React web app, Next.js, or **any Vite + React project** (load before package install — contains required `vite.config.ts` with `optimizeDeps.exclude` and `worker.format: 'es'`) | `references/sdks/powersync-js-react.md` | | React Native / Expo | React Native, Expo, or Expo Go | `references/sdks/powersync-js-react-native.md` | | Vue / Nuxt | Vue or Nuxt | `references/sdks/powersync-js-vue.md` | | Node.js / Electron | Node.js CLI/server or Electron | `references/sdks/powersync-js-node.md` | diff --git a/skills/powersync/SKILL.md b/skills/powersync/SKILL.md index a2eb34e..4ecef04 100644 --- a/skills/powersync/SKILL.md +++ b/skills/powersync/SKILL.md @@ -43,13 +43,13 @@ Use this skill to onboard a project onto PowerSync without trial-and-error. Trea Always load `references/sdks/powersync-js.md` for any JS/TS project, then load the applicable framework file. -| Framework | File | -|-----------|------| -| React / Next.js | `references/sdks/powersync-js-react.md` | -| React Native / Expo | `references/sdks/powersync-js-react-native.md` | -| Vue / Nuxt | `references/sdks/powersync-js-vue.md` | -| Node.js / Electron | `references/sdks/powersync-js-node.md` | -| TanStack | `references/sdks/powersync-js-tanstack.md` | +| Framework | File | Load early if… | +|-----------|------|----------------| +| React / Next.js | `references/sdks/powersync-js-react.md` | Vite + React project — contains the required `vite.config.ts` setup (`optimizeDeps.exclude`, `worker.format: 'es'`) needed before installing packages | +| React Native / Expo | `references/sdks/powersync-js-react-native.md` | | +| Vue / Nuxt | `references/sdks/powersync-js-vue.md` | | +| Node.js / Electron | `references/sdks/powersync-js-node.md` | | +| TanStack | `references/sdks/powersync-js-tanstack.md` | | ### Other SDKs diff --git a/skills/powersync/references/onboarding-custom.md b/skills/powersync/references/onboarding-custom.md index b0a0153..f9ad9e9 100644 --- a/skills/powersync/references/onboarding-custom.md +++ b/skills/powersync/references/onboarding-custom.md @@ -39,7 +39,9 @@ Follow this sequence exactly. **Do not skip ahead to app code.** 3. **Write credentials to `.env` immediately.** As soon as database details are available: ``` POWERSYNC_URL=https://your-instance.powersync.journeyapps.com # or http://localhost:8080 for self-hosted - PS_DATABASE_URI=postgresql://user:pass@host:5432/db + # Cloud service.yaml uses PS_DATABASE_URI; self-hosted Docker uses PS_DATA_SOURCE_URI + PS_DATABASE_URI=postgresql://user:pass@host:5432/db # Cloud + # PS_DATA_SOURCE_URI=postgresql://user:pass@host:5432/db # Self-hosted (set in powersync/docker/.env) BACKEND_URL=http://localhost:3001 ``` Both `service.yaml` (via `!env` tags) and app code depend on these values. diff --git a/skills/powersync/references/onboarding-supabase.md b/skills/powersync/references/onboarding-supabase.md index 9af83e8..dbfdb11 100644 --- a/skills/powersync/references/onboarding-supabase.md +++ b/skills/powersync/references/onboarding-supabase.md @@ -33,7 +33,7 @@ Follow this sequence exactly. **Do not skip ahead to app code.** 1. **Confirm the path.** PowerSync Cloud + Supabase + your platform. -2. **Write credentials to `.env` immediately.** As soon as Supabase project details are available, write `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `PS_DATABASE_URI`, and `POWERSYNC_URL` to `.env`. Both `service.yaml` (via `!env` tags) and app code depend on these values. +2. **Write credentials to `.env` immediately.** As soon as Supabase project details are available, write `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `PS_DATABASE_URI`, and `POWERSYNC_URL` to `.env`. Both `service.yaml` (via `!env` tags) and app code depend on these values. For how to get `POWERSYNC_URL`, see `references/powersync-cli.md` § "Getting POWERSYNC_URL". 3. **Run the Supabase publication SQL.** The publication must exist before PowerSync connects to the database. See `references/supabase-auth.md` § "Supabase Database Setup" for the exact SQL. Present it to the user and ask them to confirm. diff --git a/skills/powersync/references/powersync-cli.md b/skills/powersync/references/powersync-cli.md index 7d4c56f..1c8e6da 100644 --- a/skills/powersync/references/powersync-cli.md +++ b/skills/powersync/references/powersync-cli.md @@ -171,6 +171,8 @@ powersync init cloud # creates powersync/ with service. # 3. Create instance and deploy powersync link cloud --create --project-id= # Add --org-id= only if token has multiple orgs +# Output: "Created Cloud instance and updated powersync/cli.yaml." +# → Construct and save POWERSYNC_URL immediately (see "Getting POWERSYNC_URL" below) powersync validate powersync deploy ``` @@ -212,6 +214,26 @@ streams: For the full sync config reference, see `references/sync-config.md`. +### Getting POWERSYNC_URL + +The client-side `POWERSYNC_URL` follows the pattern `https://.powersync.journeyapps.com`. + +**New instance** — the instance ID is printed when you create it. Construct and save the URL immediately: +```bash +powersync link cloud --create --project-id= +# Output: "Created Cloud instance 69c3d035b5b902d469b2b47f and updated powersync/cli.yaml." +# → POWERSYNC_URL=https://69c3d035b5b902d469b2b47f.powersync.journeyapps.com +``` + +**Existing instance** — retrieve the ID from `powersync fetch instances`: +```bash +powersync fetch instances +# Note the instance id, e.g. "69a961b47c4f8b306a18fb7e" +# → POWERSYNC_URL=https://69a961b47c4f8b306a18fb7e.powersync.journeyapps.com +``` + +Write it to `.env` as `POWERSYNC_URL=https://.powersync.journeyapps.com` before writing any app code. + ### Existing Cloud Instance **Information the agent must collect from the user:** diff --git a/skills/powersync/references/supabase-auth.md b/skills/powersync/references/supabase-auth.md index 47f1e38..de081b4 100644 --- a/skills/powersync/references/supabase-auth.md +++ b/skills/powersync/references/supabase-auth.md @@ -207,6 +207,53 @@ async fetchCredentials(): Promise { `fetchCredentials` is called automatically on reconnect — always return a fresh token, never a cached one. +### `uploadData()` — Writing Changes Back to Supabase + +For Supabase backends, `uploadData` writes client-side changes directly to Supabase using the Supabase JS client. **`transaction.complete()` is mandatory** — without it the upload queue stalls permanently. + +```ts +import type { AbstractPowerSyncDatabase, PowerSyncBackendConnector, CrudEntry, UpdateType } from '@powersync/web'; + +export const connector: PowerSyncBackendConnector = { + async fetchCredentials() { /* ... see above ... */ }, + + async uploadData(database: AbstractPowerSyncDatabase): Promise { + const transaction = await database.getNextCrudTransaction(); + if (!transaction) return; + + try { + for (const op of transaction.crud) { + const { op: opType, table, opData, id } = op; + if (opType === UpdateType.PUT) { + const { error } = await supabase.from(table).upsert({ ...opData, id }); + if (error) throw error; + } else if (opType === UpdateType.PATCH) { + const { error } = await supabase.from(table).update(opData).eq('id', id); + if (error) throw error; + } else if (opType === UpdateType.DELETE) { + const { error } = await supabase.from(table).delete().eq('id', id); + if (error) throw error; + } + } + await transaction.complete(); // REQUIRED — clears the queue entry + } catch (error) { + // For 4xx errors (permanent failures), complete the transaction to avoid + // blocking the queue. For 5xx/network errors, throw to trigger a retry. + console.error('Upload error', error); + throw error; + } + } +}; +``` + +**Important:** RLS policies on your Supabase tables must allow the authenticated user to write their own rows. If `uploadData` consistently gets 4xx errors, the queue stalls — call `transaction.complete()` and log the error rather than retrying forever. + +### Getting the PowerSync Instance URL + +See `references/powersync-cli.md` § "Getting POWERSYNC_URL" — the instance ID is printed by `powersync link cloud --create` and the URL pattern is `https://.powersync.journeyapps.com`. Write it to `.env` before writing app code. + +For self-hosted, the URL is whatever hostname your PowerSync Docker service is exposed on (e.g. `http://localhost:8080`). + --- ## `auth.user_id()` in Sync Streams From 2d6fc537777d4bea0afa6ae0f168f535ce9ea33f Mon Sep 17 00:00:00 2001 From: bean1352 Date: Tue, 7 Apr 2026 13:52:27 +0200 Subject: [PATCH 7/9] Tighten agent skill structure: dedup, enforce Sync Streams, fix triggering --- AGENTS.md | 2 +- skills/powersync/AGENTS.md | 109 +------ skills/powersync/SKILL.md | 6 +- skills/powersync/references/attachments.md | 11 + skills/powersync/references/custom-backend.md | 9 + .../powersync/references/onboarding-custom.md | 2 + .../references/onboarding-supabase.md | 10 +- skills/powersync/references/powersync-cli.md | 14 + .../powersync/references/powersync-debug.md | 2 + .../references/powersync-overview.md | 2 + .../powersync/references/powersync-service.md | 10 + skills/powersync/references/raw-tables.md | 282 ++++++++++++++++++ .../references/sdks/powersync-dart.md | 2 + .../references/sdks/powersync-dotnet.md | 12 + .../references/sdks/powersync-js-node.md | 2 + .../references/sdks/powersync-js-orm.md | 97 ++++++ .../sdks/powersync-js-react-native.md | 2 + .../references/sdks/powersync-js-react.md | 10 + .../references/sdks/powersync-js-tanstack.md | 2 + .../references/sdks/powersync-js-vue.md | 6 + .../powersync/references/sdks/powersync-js.md | 234 +++------------ .../references/sdks/powersync-kotlin.md | 12 + .../references/sdks/powersync-swift.md | 2 + skills/powersync/references/supabase-auth.md | 68 +++-- skills/powersync/references/sync-config.md | 6 + 25 files changed, 597 insertions(+), 317 deletions(-) create mode 100644 skills/powersync/references/raw-tables.md create mode 100644 skills/powersync/references/sdks/powersync-js-orm.md diff --git a/AGENTS.md b/AGENTS.md index 5ccbd4e..8b7beab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,6 @@ That file includes: **Do not** assume Supabase, assume self-hosted Docker, or skip CLI steps unless the user **explicitly** opts out. -**`powersync login`** authenticates the CLI to **PowerSync Cloud** only (personal access token). It is **not** how you “log in” to a **self-hosted** PowerSync service. Self-hosted doesnt not require login +**`powersync login`** is **PowerSync Cloud only**. Self-hosted does not use it — see `skills/powersync/references/powersync-cli.md` § “Authentication”. When editing files under `skills/powersync/`, preserve and strengthen playbook language so agents cannot reasonably treat references as optional recipes. diff --git a/skills/powersync/AGENTS.md b/skills/powersync/AGENTS.md index 0150cd0..17a2196 100644 --- a/skills/powersync/AGENTS.md +++ b/skills/powersync/AGENTS.md @@ -12,26 +12,16 @@ Use this skill to onboard a project onto PowerSync without trial-and-error. Trea | Use the **PowerSync CLI** to scaffold, link (if cloud hosted), and deploy (`references/powersync-cli.md`) | Hand-write `service.yaml` / `sync-config.yaml` from scratch or invent compose files **unless** the user explicitly says they cannot use the CLI | | **Stop and ask** when a step needs credentials or interactive Cloud login you cannot perform | Silently build an alternate stack (e.g. manual Docker) without user confirmation | | Complete **backend readiness** (deployed sync config, auth, publication) **before** app code | Start React/client integration while sync is still unconfigured | +| Use **Sync Streams** (`config: edition: 3`) for new projects | Generate legacy Sync Rules YAML for new projects | If the user wants a shortcut, they must **say so explicitly** (e.g. “I can’t use the CLI, give dashboard steps only”). ## Always Use the PowerSync CLI -**The [PowerSync CLI](https://docs.powersync.com/tools/cli.md) is the default tool for all PowerSync operations.** Do not manually create config files, do not direct users to the dashboard, and do not write service.yaml or sync-config.yaml from scratch. The CLI handles all of this. +**The [PowerSync CLI](https://docs.powersync.com/tools/cli.md) is the default tool for all PowerSync operations.** Do not manually create config files, do not direct users to the dashboard, and do not write `service.yaml` or `sync-config.yaml` from scratch. Only fall back to manual config or dashboard instructions when the user explicitly says they can't use the CLI. -What the CLI does — use it instead of doing these things manually: - -- **Create a Cloud instance:** `powersync init cloud` → `powersync link cloud --create --project-id=` (scaffolds config, creates the instance, and links it in one flow) -- **Link an existing Cloud instance:** `powersync pull instance --project-id= --instance-id=` (downloads config to local files) -- **Create a local self-hosted instance:** `powersync init self-hosted` → `powersync docker configure` → `powersync docker start` (spins up a full local PowerSync stack via Docker — no manual Docker setup needed) -- **Deploy config:** `powersync deploy service-config`, `powersync deploy sync-config` (validates and deploys — don't copy-paste YAML to a dashboard) -- **Generate client schema:** `powersync generate schema --output=ts` (generates TypeScript schema from deployed sync config — don't write it by hand) -- **Generate dev tokens:** `powersync generate token --subject=user-1` (for local testing — don't hardcode JWTs) - -Only fall back to manual config or dashboard instructions when the user explicitly says they can't use the CLI. - -Full CLI reference: `references/powersync-cli.md` — **always load this file** when setting up or modifying a PowerSync instance. +**Always load `references/powersync-cli.md`** when setting up or modifying a PowerSync instance — it contains the full command reference for Cloud, self-hosted, and Docker workflows. ## Onboarding Playbook @@ -138,92 +128,17 @@ If the backend is Supabase, also load `references/supabase-auth.md`. ### Path 3: Self-Hosted + CLI (Recommended) -**Not Cloud:** do not use **`powersync login`** as the way to “log in” to self-hosted — that command stores a **PowerSync Cloud** PAT. Self-hosted uses **`powersync init self-hosted`**, **`powersync docker configure`**, **`powersync docker start`**, and the service’s **`PS_ADMIN_TOKEN`** for admin API access. - -Load `references/powersync-cli.md`, `references/powersync-service.md`, and `references/sync-config.md`. Prefer the CLI for Docker runs (`powersync docker run`, `powersync docker reset`), schema generation, and any supported self-hosted operations. See [PowerSync CLI](https://docs.powersync.com/tools/cli.md). +Load `references/powersync-cli.md`, `references/powersync-service.md`, and `references/sync-config.md`. Prefer the CLI for Docker runs (`powersync docker run`, `powersync docker reset`), schema generation, and any supported self-hosted operations. Remember: **`powersync login` is Cloud-only** — see `references/powersync-cli.md` § “Authentication” for self-hosted auth. ### Path 4: Self-Hosted + Manual Docker Only when the CLI cannot be used. Load `references/powersync-service.md` and `references/sync-config.md`. If the backend is **not** Supabase, also load `references/custom-backend.md`. -## Architecture - -```mermaid -flowchart LR - - subgraph BACKEND["Your Backend"] - direction TB - DB["Backend Database (Postgres | MongoDB | MySQL | Supabase | …)"] - API["Backend API (Your server / cloud functions)"] - API -- "Applies writes" --> DB - end - - subgraph PS_SERVICE["PowerSync Service"] - direction TB - SYNC["Partial Sync (sync rules filter data per user)"] - end - - subgraph APP["Your App"] - direction TB - SDK["PowerSync SDK"] - SQLITE["In-app SQLite (local replica — reads are instant)"] - QUEUE["Upload Queue (offline write buffer)"] - UI["UI"] - SDK --- SQLITE - SDK --- QUEUE - SQLITE <--> UI - QUEUE <--> UI - end - - DB -- "Replicates changes (CDC / logical replication)" --> PS_SERVICE - PS_SERVICE -- "Streams changes (real-time sync)" --> SDK - QUEUE -- "Uploads writes (when connectivity resumes)" --> API -``` - -Key rule: **client writes never go through PowerSync**. They go from the app's upload queue to your backend API. PowerSync handles the read and sync path only. - -## What to Load for Your Task - -| Task | Start with | Load on demand | -|------|-----------|----------------| -| Supabase + PowerSync | `references/onboarding-supabase.md` | `references/supabase-auth.md`, `references/sync-config.md`, SDK files (when writing app code) | -| Custom backend (non-Supabase) | `references/onboarding-custom.md` | `references/custom-backend.md`, `references/sync-config.md`, SDK files (when writing app code) | -| New project setup | `references/powersync-cli.md` + `references/powersync-service.md` | `references/sync-config.md`, SDK files (when writing app code) | -| Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` | `references/sync-config.md` | -| Writing sync config | `references/sync-config.md` | — | -| Debugging sync issues | `references/powersync-debug.md` | — | -| Attachments | `references/attachments.md` | — | -| Architecture overview | This file is sufficient | `references/powersync-overview.md` for deep links | - -## SDK Reference Files - -### JavaScript / TypeScript - -Always load `references/sdks/powersync-js.md` for any JS/TS project, then load the applicable framework file. - -| Framework | Load when… | File | -|-----------|-----------|------| -| React / Next.js | React web app, Next.js, or **any Vite + React project** (load before package install — contains required `vite.config.ts` with `optimizeDeps.exclude` and `worker.format: 'es'`) | `references/sdks/powersync-js-react.md` | -| React Native / Expo | React Native, Expo, or Expo Go | `references/sdks/powersync-js-react-native.md` | -| Vue / Nuxt | Vue or Nuxt | `references/sdks/powersync-js-vue.md` | -| Node.js / Electron | Node.js CLI/server or Electron | `references/sdks/powersync-js-node.md` | -| TanStack | TanStack Query or TanStack DB | `references/sdks/powersync-js-tanstack.md` | - -### Other SDKs - -| Platform | Load when… | File | -|----------|-----------|------| -| Dart / Flutter | Dart / Flutter | `references/sdks/powersync-dart.md` | -| .NET | .NET | `references/sdks/powersync-dotnet.md` | -| Kotlin | Kotlin | `references/sdks/powersync-kotlin.md` | -| Swift | Swift / iOS / macOS | `references/sdks/powersync-swift.md` | - -## Key Rules to Apply Without Being Asked - -- Use Sync Streams for new projects. Sync Rules are legacy. -- Never define the `id` column in a PowerSync table schema; it is created automatically. -- Use `column.integer` for booleans and `column.text` for ISO date strings. -- `connect()` is fire-and-forget. Use `waitForFirstSync()` if you need readiness. -- `transaction.complete()` is mandatory or the upload queue stalls permanently. -- `disconnectAndClear()` is required on logout or user switch when local data must be wiped. -- A 4xx response from `uploadData` blocks the upload queue permanently; return 2xx for validation errors. +## Architecture, Routing, SDK Tables & Key Rules + +These are defined once in **SKILL.md** — refer there for: + +- **Architecture diagram** — shows the read/sync path (PowerSync Service → SDK) and write path (upload queue → your backend API) +- **"What to Load for Your Task"** table — maps tasks to starter files and on-demand files +- **SDK Reference Files** tables — maps frameworks/platforms to reference files +- **"Key Rules to Apply Without Being Asked"** — `id` column, `connect()`, `transaction.complete()`, `disconnectAndClear()`, 4xx upload handling diff --git a/skills/powersync/SKILL.md b/skills/powersync/SKILL.md index 4ecef04..134aed8 100644 --- a/skills/powersync/SKILL.md +++ b/skills/powersync/SKILL.md @@ -1,6 +1,6 @@ --- name: powersync -description: Guided onboarding and best practices for building applications with PowerSync — Cloud and self-hosted setup, sync configuration, client SDK usage, backend integration (Supabase, custom Postgres, MongoDB, MySQL, MSSQL), and debugging. +description: Guided onboarding and best practices for building applications with PowerSync — Cloud and self-hosted setup, sync configuration, client SDK usage, backend integration (Supabase, custom Postgres, MongoDB, MySQL, MSSQL), and debugging. Use this skill whenever the user mentions PowerSync, offline-first sync, local-first architecture, sync rules, sync streams, uploadData, fetchCredentials, real-time data replication, or wants to add offline-capable sync to a mobile or web app — even if they don't explicitly name PowerSync. license: MIT compatibility: Works with any skills-compatible agent. Some references include CLI commands requiring the @powersync/cli package. metadata: @@ -14,7 +14,7 @@ metadata: Use this skill to onboard a project onto PowerSync without trial-and-error. Treat this as a guided workflow first and a reference library second. -**Agents:** Follow **[AGENTS.md](AGENTS.md)** in full — including **Agent compliance** (ask Cloud vs self-hosted, ask backend if unspecified, CLI-first, no silent shortcuts). **`powersync login`** is **PowerSync Cloud only** (PAT); self-hosted does not use it. +**Agents: Read [AGENTS.md](AGENTS.md) before proceeding.** It contains the mandatory compliance rules and onboarding playbook. The Quick Rules below are a reminder, not a substitute. **`powersync login`** is **PowerSync Cloud only** (PAT); self-hosted does not use it. ## Quick Rules @@ -34,6 +34,7 @@ Use this skill to onboard a project onto PowerSync without trial-and-error. Trea | Self-hosting / service config | `references/powersync-service.md` + `references/powersync-cli.md` | `references/sync-config.md` | | Writing sync config | `references/sync-config.md` | — | | Debugging sync issues | `references/powersync-debug.md` | — | +| Raw Tables (advanced) | `references/raw-tables.md` | — | | Attachments | `references/attachments.md` | — | | Architecture overview | `references/powersync-overview.md` | — | @@ -50,6 +51,7 @@ Always load `references/sdks/powersync-js.md` for any JS/TS project, then load t | Vue / Nuxt | `references/sdks/powersync-js-vue.md` | | | Node.js / Electron | `references/sdks/powersync-js-node.md` | | | TanStack | `references/sdks/powersync-js-tanstack.md` | | +| Drizzle / Kysely ORM | `references/sdks/powersync-js-orm.md` | Project uses Drizzle or Kysely | ### Other SDKs diff --git a/skills/powersync/references/attachments.md b/skills/powersync/references/attachments.md index 58dd173..0cf0842 100644 --- a/skills/powersync/references/attachments.md +++ b/skills/powersync/references/attachments.md @@ -7,6 +7,17 @@ metadata: # PowerSync Attachments +> **Load this when** the app needs file uploads/downloads (images, documents, media) synced alongside PowerSync data. + +## Table of Contents +- [How It Works](#how-it-works) +- [Package Setup](#package-setup) +- [Schema Setup](#schema-setup) +- [Storage Adapters](#storage-adapters) +- [Initialize the Attachment Queue](#initialize-the-attachment-queue) +- [Upload / Delete / Access Files](#upload-a-file) +- [Error Handling](#error-handling) + PowerSync handles file attachments using a **metadata + storage provider** pattern: structured metadata syncs through PowerSync while actual files live in a purpose-built storage system (S3, Supabase Storage, Cloudflare R2, etc.). An offline-first queue manages uploads, downloads, and retries automatically in the background. | Resource | Description | diff --git a/skills/powersync/references/custom-backend.md b/skills/powersync/references/custom-backend.md index 7a4c9f3..41ddd04 100644 --- a/skills/powersync/references/custom-backend.md +++ b/skills/powersync/references/custom-backend.md @@ -7,6 +7,15 @@ metadata: # Custom Backend for PowerSync +> **Load this when** building a PowerSync integration without Supabase — custom auth, custom backend API, or any non-Supabase database. + +## Table of Contents +- [Architecture Recap](#architecture-recap) +- [1. Custom JWT Auth](#1-custom-jwt-auth) +- [2. Backend API for uploadData](#2-backend-api-for-uploaddata) +- [3. Client-Side Connector](#3-client-side-connector-custom-backend) +- [Common Pitfalls](#common-pitfalls) + Use this file when building a PowerSync integration **without Supabase** — your own auth and a backend API that receives writes from the client's upload queue. For **source database setup** (Postgres replication, MongoDB replica set, MySQL binlog, MSSQL CDC), see `references/powersync-service.md` § "Source Database Setup". diff --git a/skills/powersync/references/onboarding-custom.md b/skills/powersync/references/onboarding-custom.md index f9ad9e9..623ff19 100644 --- a/skills/powersync/references/onboarding-custom.md +++ b/skills/powersync/references/onboarding-custom.md @@ -7,6 +7,8 @@ metadata: # Custom Backend + PowerSync Onboarding +> **Load this when** onboarding an app onto PowerSync with a non-Supabase backend (custom Postgres, MongoDB, MySQL, MSSQL). + Use this recipe when onboarding any app onto PowerSync with a **non-Supabase backend** — your own database, your own auth, and your own backend API. Works for all platforms (web, React Native, Flutter, Kotlin, Swift, .NET, etc.) and both Cloud and self-hosted. **CLI-first.** See `references/powersync-cli.md`. Fall back to the dashboard (Cloud) or manual Docker config (self-hosted) only if the CLI is unavailable or the user explicitly prefers it. diff --git a/skills/powersync/references/onboarding-supabase.md b/skills/powersync/references/onboarding-supabase.md index dbfdb11..7672604 100644 --- a/skills/powersync/references/onboarding-supabase.md +++ b/skills/powersync/references/onboarding-supabase.md @@ -7,6 +7,8 @@ metadata: # Supabase + PowerSync Cloud Onboarding +> **Load this when** onboarding an app onto PowerSync Cloud with a Supabase backend. + Use this recipe when onboarding any app onto PowerSync Cloud with a Supabase backend. This works for all platforms (web, React Native, Flutter, Kotlin, Swift, .NET, etc.). **CLI-first.** See `references/powersync-cli.md`. Fall back to the dashboard only if the CLI is unavailable or the user explicitly prefers it. @@ -81,13 +83,17 @@ Only after Phase 2 is complete. ``` Or write manually — but never define the `id` column (it is automatic). -11. **Implement the backend connector.** See `references/supabase-auth.md` § "fetchCredentials()" for the Supabase-specific implementation. For `uploadData`, Supabase users can write directly to Supabase via the client library or use Edge Functions. +11. **Implement the backend connector.** See `references/supabase-auth.md` § "fetchCredentials()" and § "uploadData()" for complete implementations including error handling strategy. + + **Auth prerequisite:** `fetchCredentials()` requires an active Supabase session. Call `db.connect(connector)` only after the user has signed in. For apps without a sign-in screen, enable anonymous auth in Supabase Dashboard → Authentication → Providers → Anonymous. 12. **Initialize PowerSync and connect.** - `connect()` is fire-and-forget — use `waitForFirstSync()` if you need readiness. - Use `disconnectAndClear()` on logout or user switch. -13. **Switch reads to local SQLite** and test offline behavior. +13. **Development tokens** (optional, for testing without user auth): After deploying, run `powersync generate token --subject=` to get a short-lived JWT. On PowerSync Cloud, this works immediately after deploy — no extra `client_auth` config needed. Do not use dev tokens in production. + +14. **Switch reads to local SQLite** and test offline behavior. ## If the App Is Stuck on `Syncing...` diff --git a/skills/powersync/references/powersync-cli.md b/skills/powersync/references/powersync-cli.md index 1c8e6da..14a14a0 100644 --- a/skills/powersync/references/powersync-cli.md +++ b/skills/powersync/references/powersync-cli.md @@ -7,6 +7,20 @@ metadata: # PowerSync CLI +> **Load this when** setting up, deploying, or managing any PowerSync instance (Cloud or self-hosted). This is the primary tool for all PowerSync operations. + +## Table of Contents +- [Recommended Defaults for Agents](#recommended-defaults-for-agents) +- [Installation](#installation) +- [Instance Resolution](#how-the-cli-resolves-instance-information) +- [Authentication](#authentication) +- [Config Files](#config-files) +- [Cloud Usage](#cloud-usage) +- [Self-Hosted Usage](#self-hosted-usage) +- [Docker Commands](#docker-commands-reference) +- [Common Commands](#common-commands) +- [Development Tokens](#development-tokens) + The PowerSync CLI manages Cloud and self-hosted PowerSync instances from the command line. It supports local config management, schema generation, development token generation, deployment, and more. See [this](https://docs.powersync.com/tools/cli.md) for any information not supplied in this document about the CLI. ## Recommended Defaults for Agents diff --git a/skills/powersync/references/powersync-debug.md b/skills/powersync/references/powersync-debug.md index 89350d6..73f20db 100644 --- a/skills/powersync/references/powersync-debug.md +++ b/skills/powersync/references/powersync-debug.md @@ -7,6 +7,8 @@ metadata: # PowerSync Debug +> **Load this when** troubleshooting sync issues, stuck "Syncing..." states, JWT errors, or replication problems. + These are debugging steps most frequently recommended by PowerSync, with an explanation of what problem each step helps identify and why it works. Make sure to understand the [PowerSync Architecture](references/powersync-overview.md) before debugging. diff --git a/skills/powersync/references/powersync-overview.md b/skills/powersync/references/powersync-overview.md index ebc0da3..0f7d121 100644 --- a/skills/powersync/references/powersync-overview.md +++ b/skills/powersync/references/powersync-overview.md @@ -7,6 +7,8 @@ metadata: # PowerSync Architecture Overview +> **Load this when** you need to understand how PowerSync's components fit together before diving into implementation. + Guidance for understanding all the moving components of PowerSync. For information about the vision of PowerSync, see [PowerSync Philosophy](https://docs.powersync.com/intro/powersync-philosophy.md) ## Architecture diff --git a/skills/powersync/references/powersync-service.md b/skills/powersync/references/powersync-service.md index f8096e6..03e2edb 100644 --- a/skills/powersync/references/powersync-service.md +++ b/skills/powersync/references/powersync-service.md @@ -7,6 +7,16 @@ metadata: # PowerSync Service +> **Load this when** configuring the PowerSync service itself — self-hosting, Docker, source database connections, bucket storage, or authentication setup. + +## Table of Contents +- [Sync Config](#sync-config) +- [Service Configuration (Self-hosted)](#service-configuration-self-hosted) +- [PowerSync Cloud Setup](#powersync-cloud-setup) +- [Source Database Setup](#source-database-setup) +- [App Backend](#app-backend) +- [Authentication](#authentication) + Guidance for configuring PowerSync Service, sync config, and database replication. Critical warnings for fast setup: diff --git a/skills/powersync/references/raw-tables.md b/skills/powersync/references/raw-tables.md new file mode 100644 index 0000000..ed287fb --- /dev/null +++ b/skills/powersync/references/raw-tables.md @@ -0,0 +1,282 @@ +--- +name: raw-tables +description: PowerSync Raw Tables — native SQLite tables bypassing JSON views, with multi-SDK examples (JS, Dart, Kotlin, Swift, Rust), triggers, local-only columns, and migration strategies +metadata: + tags: raw-tables, sqlite, advanced, powersync, javascript, dart, kotlin, swift, rust +--- + +# Raw Tables + +> **Load this when** the project needs native SQLite tables (column types, constraints, indexes, generated columns) instead of PowerSync's default JSON-based views. Works across all SDKs except .NET. + +Raw tables let PowerSync sync data directly into native SQLite tables you define, instead of storing data as JSON in `ps_data__` and exposing it via views. This gives full SQLite control and better query performance. See [Raw Tables](https://docs.powersync.com/client-sdks/advanced/raw-tables.md) for the full reference. + +## Table of Contents +- [When to Use](#when-to-use-raw-tables) +- [SDK Availability](#sdk-availability) +- [Defining Raw Tables](#defining-raw-tables) (Inferred vs Explicit) +- [Triggers for Local Writes](#triggers-for-local-writes) (Inferred vs Explicit) +- [Local-Only Columns](#local-only-columns) +- [Migrations](#migrations) +- [Caveats](#caveats) + +**Status:** Experimental — not covered by semver stability guarantees. + +## SDK Availability + +| SDK | Min Version | Package | +|-----|-------------|---------| +| JavaScript (Web) | 1.35.0 | `@powersync/web` | +| JavaScript (React Native) | 1.31.0 | `@powersync/react-native` | +| JavaScript (Node) | 0.18.0 | `@powersync/node` | +| Dart / Flutter | 1.18.0 | `package:powersync` | +| Kotlin | 1.11.0 | `com.powersync:core` | +| Swift | 1.12.0 | `PowerSync` | +| Rust | 0.0.4 | `powersync` | +| .NET | — | **Not yet available** | + +## When to Use Raw Tables + +- Indexes on expressions or `GENERATED` columns (PowerSync's default schema only supports basic column indexes) +- Improved query performance for aggregations (`SUM`, `GROUP BY`) — reads typed columns directly instead of extracting from JSON +- Reduced storage overhead — no JSON object per row +- SQLite constraints (`FOREIGN KEY`, `NOT NULL`, `CHECK`) +- Local-only columns that persist across syncs but never upload + +## Defining Raw Tables + +You must create the actual SQLite table yourself before calling `connect()`: + +```sql +CREATE TABLE IF NOT EXISTS todo_lists ( + id TEXT NOT NULL PRIMARY KEY, + created_by TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT +) STRICT; +``` + +### Inferred Setup (Recommended) + +When the local table structure matches the synced table, the SDK can infer `put`/`delete` statements automatically: + +**JavaScript:** +```ts +const mySchema = new Schema({}); +mySchema.withRawTables({ + todo_lists: { schema: {} } +}); +``` + +**Dart:** +```dart +const schema = Schema([], rawTables: [ + RawTable.inferred(name: 'todo_lists', schema: RawTableSchema()), +]); +``` + +**Kotlin:** +```kotlin +val schema = Schema(listOf( + RawTable(name = "todo_lists", schema = RawTableSchema()) +)) +``` + +**Swift:** +```swift +let lists = RawTable(name: "todo_lists", schema: RawTableSchema()) +let schema = Schema(lists) +``` + +**Rust:** +```rust +let table = RawTable::with_schema("todo_lists", RawTableSchema::default()); +schema.raw_tables.push(table); +``` + +Use inferred setup when the local table directly maps to the synced output table. Use explicit setup (below) for transformations, custom defaults, the `_extra` column pattern, or when local and backend table names differ. + +### Explicit Setup + +Provide `put` and `delete` SQL statements with positional parameters: + +**JavaScript:** +```ts +mySchema.withRawTables({ + todo_lists: { + put: { + sql: 'INSERT OR REPLACE INTO todo_lists (id, created_by, title, content) VALUES (?, ?, ?, ?)', + params: ['Id', { Column: 'created_by' }, { Column: 'title' }, { Column: 'content' }] + }, + delete: { + sql: 'DELETE FROM todo_lists WHERE id = ?', + params: ['Id'] + } + } +}); +``` + +**Dart:** +```dart +RawTable( + name: 'todo_lists', + put: PendingStatement( + sql: 'INSERT OR REPLACE INTO todo_lists (id, created_by, title, content) VALUES (?, ?, ?, ?)', + params: [.id(), .column('created_by'), .column('title'), .column('content')], + ), + delete: PendingStatement(sql: 'DELETE FROM todo_lists WHERE id = ?', params: [.id()]), +) +``` + +**Kotlin:** +```kotlin +RawTable( + name = "todo_lists", + put = PendingStatement( + "INSERT OR REPLACE INTO todo_lists (id, created_by, title, content) VALUES (?, ?, ?, ?)", + listOf(PendingStatementParameter.Id, PendingStatementParameter.Column("created_by"), + PendingStatementParameter.Column("title"), PendingStatementParameter.Column("content")) + ), + delete = PendingStatement("DELETE FROM todo_lists WHERE id = ?", listOf(PendingStatementParameter.Id)) +) +``` + +**Swift:** +```swift +RawTable( + name: "todo_lists", + put: PendingStatement( + sql: "INSERT OR REPLACE INTO todo_lists (id, created_by, title, content) VALUES (?, ?, ?, ?)", + parameters: [.id, .column("created_by"), .column("title"), .column("content")] + ), + delete: PendingStatement(sql: "DELETE FROM todo_lists WHERE id = ?", parameters: [.id]) +) +``` + +Parameter types: `Id` = row ID from sync service, `Column("name")` = column value from synced row, `Rest` = remaining columns as JSON (for the `_extra` pattern). + +## Triggers for Local Writes + +Raw tables need triggers to capture local writes into PowerSync's upload queue (`powersync_crud` virtual table). + +### Inferred Triggers (Recommended) + +Use `powersync_create_raw_table_crud_trigger` — must be called **after** the `CREATE TABLE`: + +**JavaScript:** +```ts +for (const write of ["INSERT", "UPDATE", "DELETE"]) { + await db.execute("SELECT powersync_create_raw_table_crud_trigger(?, ?, ?)", + [JSON.stringify(Schema.rawTableToJson(table)), `todo_lists_${write}`, write]); +} +``` + +**Dart:** +```dart +for (final write in ["INSERT", "UPDATE", "DELETE"]) { + await db.execute("SELECT powersync_create_raw_table_crud_trigger(?, ?, ?)", + [json.encode(table), "todo_lists_$write", write]); +} +``` + +**Kotlin:** +```kotlin +for (write in listOf("INSERT", "UPDATE", "DELETE")) { + database.execute("SELECT powersync_create_raw_table_crud_trigger(?, ?, ?)", + listOf(table.jsonDescription(), "todo_lists_$write", write)) +} +``` + +**Swift:** +```swift +for write in ["INSERT", "UPDATE", "DELETE"] { + try await database.execute( + sql: "SELECT powersync_create_raw_table_crud_trigger(?, ?, ?)", + parameters: [lists.jsonDescription(), "todo_lists_\(write)", write]) +} +``` + +### Explicit Triggers + +Define triggers manually for full control: + +```sql +CREATE TRIGGER todo_lists_insert AFTER INSERT ON todo_lists FOR EACH ROW +BEGIN + INSERT INTO powersync_crud (op, id, type, data) + VALUES ('PUT', NEW.id, 'todo_lists', json_object( + 'created_by', NEW.created_by, 'title', NEW.title, 'content', NEW.content)); +END; + +CREATE TRIGGER todo_lists_update AFTER UPDATE ON todo_lists FOR EACH ROW +BEGIN + SELECT CASE WHEN (OLD.id != NEW.id) THEN RAISE(FAIL, 'Cannot update id') END; + INSERT INTO powersync_crud (op, id, type, data) + VALUES ('PATCH', NEW.id, 'todo_lists', json_object( + 'created_by', NEW.created_by, 'title', NEW.title, 'content', NEW.content)); +END; + +CREATE TRIGGER todo_lists_delete AFTER DELETE ON todo_lists FOR EACH ROW +BEGIN + INSERT INTO powersync_crud (op, id, type) VALUES ('DELETE', OLD.id, 'todo_lists'); +END; +``` + +The `powersync_crud` virtual table columns: `op` (PUT/PATCH/DELETE), `id`, `type` (table name), `data` (JSON), `old_values` (optional), `metadata` (optional). + +## Local-Only Columns + +Raw tables can include columns that exist only on the client — never synced or uploaded. Useful for client preferences, UI state, or local notes. + +Add the column to the table and specify `syncedColumns` in the inferred setup so the SDK knows which columns come from the server: + +**JavaScript:** +```ts +{ name: 'todo_lists', schema: { syncedColumns: ['created_by', 'title', 'content'] } } +``` + +**Dart:** +```dart +RawTableSchema(syncedColumns: ['created_by', 'title', 'content']) +``` + +**Kotlin:** +```kotlin +RawTableSchema(syncedColumns = listOf("created_by", "title", "content")) +``` + +With explicit setup, use `INSERT ... ON CONFLICT(id) DO UPDATE SET` (not `INSERT OR REPLACE`) to avoid resetting local-only columns on sync. Exclude local-only columns from triggers. + +## Migrations + +PowerSync's JSON-based tables need no migrations. Raw tables do — you manage the schema. + +### Adding a new raw table + +If data was already synced before the raw table existed, it's in `ps_untyped`. Copy it after creating the table: + +```sql +INSERT INTO my_table (id, col1, col2) + SELECT id, data ->> 'col1', data ->> 'col2' + FROM ps_untyped WHERE type = 'my_table'; +DELETE FROM ps_untyped WHERE type = 'my_table'; +``` + +Not needed if the raw table was present from the first `connect()` call. + +### Adding columns + +Three strategies: + +1. **Delete and resync:** `disconnectAndClear(soft: true)` → migrate → reconnect. Safest but requires network. +2. **Trigger resync:** `ALTER TABLE ... ADD COLUMN` with a default → `SELECT powersync_trigger_resync(TRUE)`. App stays usable offline with optimistic defaults until resync completes. +3. **`_extra` column pattern:** Store unknown columns as JSON in an `_extra TEXT` column using the `Rest` parameter. Migrate by extracting from `_extra`: `json_extract(_extra, '$.newCol')`. + +## Caveats + +- **Not available on .NET** yet +- **No automatic column migration** — adding columns requires one of the migration strategies above +- **Foreign keys** — must use `DEFERRABLE INITIALLY DEFERRED`; enable with `PRAGMA foreign_keys = ON`; avoid FK references from high-priority to lower-priority raw tables +- **`disconnectAndClear()` won't clear raw tables** by default — add a `clear` statement to `RawTable` if needed +- **Table name** — the `name` property matches the backend table name, not necessarily the local SQLite table name +- **Drop and re-create triggers** after altering a raw table diff --git a/skills/powersync/references/sdks/powersync-dart.md b/skills/powersync/references/sdks/powersync-dart.md index 4f55325..d48c29d 100644 --- a/skills/powersync/references/sdks/powersync-dart.md +++ b/skills/powersync/references/sdks/powersync-dart.md @@ -7,6 +7,8 @@ metadata: # PowerSync Dart SDK +> **Load this when** building a Flutter or Dart app with PowerSync. + Best practices and guidance for building Flutter apps with the PowerSync Dart SDK. | Resource | Description | diff --git a/skills/powersync/references/sdks/powersync-dotnet.md b/skills/powersync/references/sdks/powersync-dotnet.md index 2b1ebb5..e5e9fe2 100644 --- a/skills/powersync/references/sdks/powersync-dotnet.md +++ b/skills/powersync/references/sdks/powersync-dotnet.md @@ -7,6 +7,18 @@ metadata: # PowerSync .NET SDK +> **Load this when** building a .NET app (MAUI, WPF, Console) with PowerSync. + +## Table of Contents +- [Installation](#installation) +- [Quick Setup](#quick-setup) +- [Query Patterns](#query-patterns) +- [Writes and Transactions](#writes-and-transactions) +- [Sync Status](#sync-status) +- [Sync Streams](#sync-streams) +- [Logging](#logging) +- [Schema Updates](#schema-updates) + Best practices for building apps with the PowerSync .NET SDK. Supported targets: .NET 9, .NET 8, .NET 6, .NET Standard 2.0, .NET Framework 4.8. Application frameworks: MAUI (iOS, Android, Windows), WPF, Console. diff --git a/skills/powersync/references/sdks/powersync-js-node.md b/skills/powersync/references/sdks/powersync-js-node.md index ea6efe4..d967e43 100644 --- a/skills/powersync/references/sdks/powersync-js-node.md +++ b/skills/powersync/references/sdks/powersync-js-node.md @@ -7,6 +7,8 @@ metadata: # PowerSync Node.js & Electron +> **Load this when** building a Node.js CLI tool, background sync process, ETL pipeline, or Electron desktop app. Always load `powersync-js.md` first. + Node.js-specific integration for the PowerSync JavaScript SDK. Use this reference alongside `references/sdks/powersync-js.md` when building Node.js CLI tools, background sync processes, ETL pipelines, or Electron desktop apps. | Resource | Description | diff --git a/skills/powersync/references/sdks/powersync-js-orm.md b/skills/powersync/references/sdks/powersync-js-orm.md new file mode 100644 index 0000000..696f090 --- /dev/null +++ b/skills/powersync/references/sdks/powersync-js-orm.md @@ -0,0 +1,97 @@ +--- +name: powersync-js-orm +description: Drizzle and Kysely ORM integration with PowerSync JavaScript SDK — type-safe queries, schema definition, and CompilableQuery usage +metadata: + tags: javascript, typescript, drizzle, kysely, orm, powersync +--- + +> **Load this when** the project uses Drizzle or Kysely ORM with PowerSync. Always load `powersync-js.md` first. + +# Drizzle & Kysely ORM Integration + +PowerSync provides official drivers for both Drizzle and Kysely. These let you write type-safe queries that work with PowerSync's reactive hooks (`useQuery`, `useSuspenseQuery`) via the `CompilableQuery` interface. + +| ORM | Package | Docs | +|-----|---------|------| +| Drizzle | `@powersync/drizzle-driver` | [Drizzle Setup](https://docs.powersync.com/client-sdks/orms/javascript-web/drizzle.md) | +| Kysely | `@powersync/kysely-driver` | [Kysely Setup](https://docs.powersync.com/client-sdks/orms/javascript-web/kysely.md) | + +## Drizzle + +```bash +npm install @powersync/drizzle-driver drizzle-orm +``` + +```ts +import { drizzle } from '@powersync/drizzle-driver'; +import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; +import { eq } from 'drizzle-orm'; + +// Define Drizzle schema — separate from the PowerSync schema +export const todos = sqliteTable('todos', { + id: text('id').primaryKey(), + description: text('description').notNull(), + completed: integer('completed').notNull().default(0), + listId: text('list_id') +}); + +// Create Drizzle instance from the PowerSync database +const drizzleDb = drizzle(db); + +// Type-safe queries +const activeTodos = await drizzleDb + .select() + .from(todos) + .where(eq(todos.completed, 0)); +``` + +### Drizzle with useQuery + +Drizzle queries implement `CompilableQuery`, so they work directly with PowerSync hooks: + +```ts +const query = drizzleDb.select().from(todos).where(eq(todos.completed, 0)); +const { data } = useQuery(query); +``` + +## Kysely + +```bash +npm install @powersync/kysely-driver kysely +``` + +```ts +import { wrapPowerSyncWithKysely } from '@powersync/kysely-driver'; + +// Define types for Kysely +interface Database { + todos: { id: string; description: string; completed: number; list_id: string }; + lists: { id: string; name: string; owner_id: string }; +} + +// Create Kysely instance from the PowerSync database +const kyselyDb = wrapPowerSyncWithKysely(db); + +// Type-safe queries +const activeTodos = await kyselyDb + .selectFrom('todos') + .selectAll() + .where('completed', '=', 0) + .execute(); +``` + +### Kysely with useQuery + +```ts +const query = kyselyDb.selectFrom('todos').selectAll().where('completed', '=', 0); +const { data } = useQuery(query); +``` + +## Both schemas are needed + +You need **two** schemas when using an ORM: + +1. **PowerSync schema** (`new Schema(...)`) — defines what data syncs. Required. +2. **ORM schema** (Drizzle `sqliteTable` or Kysely interface) — provides type safety for queries. Optional but recommended. + +These must stay in sync — if you add a column to the PowerSync schema, add it to the ORM schema too. diff --git a/skills/powersync/references/sdks/powersync-js-react-native.md b/skills/powersync/references/sdks/powersync-js-react-native.md index 5b57db4..14473da 100644 --- a/skills/powersync/references/sdks/powersync-js-react-native.md +++ b/skills/powersync/references/sdks/powersync-js-react-native.md @@ -7,6 +7,8 @@ metadata: # PowerSync React Native, Expo & Expo Go +> **Load this when** building a React Native app, Expo app (managed or bare), or Expo Go sandbox. Always load `powersync-js.md` first. + React Native-specific integration for the PowerSync JavaScript SDK. Use this reference alongside `references/sdks/powersync-js.md` when building React Native apps, Expo apps (managed or bare workflow), or Expo Go sandboxes. The React hooks API (`useQuery`, `useStatus`, `usePowerSync`, `useSuspenseQuery`) from `@powersync/react-native` is identical to `@powersync/react` — see `references/sdks/powersync-js.md` for full hook patterns and `references/sdks/powersync-js-react.md` for `useSuspenseQuery` and sync stream hooks. diff --git a/skills/powersync/references/sdks/powersync-js-react.md b/skills/powersync/references/sdks/powersync-js-react.md index e31f463..c01f527 100644 --- a/skills/powersync/references/sdks/powersync-js-react.md +++ b/skills/powersync/references/sdks/powersync-js-react.md @@ -7,6 +7,16 @@ metadata: # PowerSync React & Next.js +> **Load this when** building a React web app, Next.js app, or any Vite + React project. Load **before** package install for Vite projects — contains the required `vite.config.ts` setup. Always load `powersync-js.md` first. + +## Table of Contents +- [Provider Setup](#provider-setup) +- [useSuspenseQuery](#usesuspensequery) +- [Sync Stream Hooks](#sync-stream-hooks) +- [Next.js Setup](#nextjs-setup) +- [Vite Setup](#vite-setup) +- [Common Pitfalls](#common-pitfalls) + React-specific integration for the PowerSync JavaScript SDK. Use this reference alongside `references/sdks/powersync-js.md` when building React web apps, Next.js apps, or when using the React hooks from `@powersync/react` or `@powersync/react-native`. | Resource | Description | diff --git a/skills/powersync/references/sdks/powersync-js-tanstack.md b/skills/powersync/references/sdks/powersync-js-tanstack.md index 6ed4ba2..092110c 100644 --- a/skills/powersync/references/sdks/powersync-js-tanstack.md +++ b/skills/powersync/references/sdks/powersync-js-tanstack.md @@ -7,6 +7,8 @@ metadata: # PowerSync TanStack Integrations +> **Load this when** using TanStack Query (React) or TanStack DB (multi-framework) with PowerSync. Always load `powersync-js.md` first. + TanStack-specific integrations for the PowerSync JavaScript SDK. Use this reference alongside `references/sdks/powersync-js.md` when using TanStack Query (React) or TanStack DB (multi-framework) with PowerSync. | Resource | Description | diff --git a/skills/powersync/references/sdks/powersync-js-vue.md b/skills/powersync/references/sdks/powersync-js-vue.md index d02c80d..a098473 100644 --- a/skills/powersync/references/sdks/powersync-js-vue.md +++ b/skills/powersync/references/sdks/powersync-js-vue.md @@ -7,6 +7,12 @@ metadata: # PowerSync Vue & Nuxt +> **Load this when** building a Vue app or Nuxt app with PowerSync. Always load `powersync-js.md` first. + +## Table of Contents +- [Vue](#vue) (Plugin, Composables, Watchers) +- [Nuxt](#nuxt) (Module, Auto-imports, SSR) + Vue-specific integration for the PowerSync JavaScript SDK. Use this reference alongside `references/sdks/powersync-js.md` when building Vue apps or Nuxt apps. | Resource | Description | diff --git a/skills/powersync/references/sdks/powersync-js.md b/skills/powersync/references/sdks/powersync-js.md index 84baf5c..83b6860 100644 --- a/skills/powersync/references/sdks/powersync-js.md +++ b/skills/powersync/references/sdks/powersync-js.md @@ -1,13 +1,31 @@ --- name: powersync-js -description: PowerSync JavaScript/TypeScript SDK — schema, backend connector, queries, transactions, sync status, raw tables, Drizzle/Kysely ORM, and debugging +description: PowerSync JavaScript/TypeScript SDK — schema, backend connector, queries, transactions, sync status, and debugging metadata: - tags: javascript, typescript, web, sqlite, offline-first, drizzle, kysely + tags: javascript, typescript, web, sqlite, offline-first --- +> **Load this when** working on any JavaScript or TypeScript project with PowerSync. This is the foundation file — always load it first, then load the applicable framework-specific file alongside it. + # PowerSync JavaScript/TypeScript SDK -Core patterns and guidance shared across all PowerSync JavaScript/TypeScript targets. Use this reference for any JS/TS project — it covers schema design, the backend connector, database initialization, transactions, imperative queries, sync status, raw tables, and debugging. Always load this file as the foundation, then load the applicable framework-specific file alongside it. +Core patterns and guidance shared across all PowerSync JavaScript/TypeScript targets — schema design, the backend connector, database initialization, transactions, imperative queries, sync status, and debugging. For ORM integration see `powersync-js-orm.md`; for raw tables and sync internals see `powersync-js-raw-tables.md`. + +## Table of Contents + +- [Package Coverage](#package-coverage) +- [Quick Setup](#quick-setup) (Install, Schema, Backend Connector, uploadData, Initialize) +- [Query Patterns](#query-patterns) (useQuery, CompilableQuery, Imperative, Watch) +- [Writes & Transactions](#writes--transactions) +- [Sync Status, Priorities & Sync Streams](#sync-status-priorities--sync-streams) +- [Debugging](#debugging) +- [Common Pitfalls](#common-pitfalls) + +**TypeScript-only exports:** `PowerSyncBackendConnector`, `PowerSyncCredentials`, and `AbstractPowerSyncDatabase` are **TypeScript interfaces only** — they exist at compile time for type checking but have no runtime presence. This means: +- Always use `import type` for these (e.g. `import type { PowerSyncBackendConnector } from '@powersync/web'`) +- Runtime checks like `require('@powersync/web').PowerSyncBackendConnector` will return `undefined` — this is expected, not a bug +- Only `UpdateType` is a runtime value (enum) and uses a regular import +- Bundlers like Vite will error if you import these without `type` since they try to resolve them as values | Resource | Description | |----------|-------------| @@ -142,8 +160,6 @@ const tasks = new Table( See [Integrate with your Backend](https://docs.powersync.com/client-sdks/reference/javascript-web.md#3-integrate-with-your-backend) and [Client-Side Integration](https://docs.powersync.com/configuration/app-backend/client-side-integration.md) for more information. -**IMPORTANT:** `PowerSyncBackendConnector`, `PowerSyncCredentials`, and `AbstractPowerSyncDatabase` are **type-only exports**. Always use `import type` for these — importing them as values (without `type`) causes runtime errors in bundlers like Vite. Only `UpdateType` is a runtime value (enum) and uses a regular import. - ```ts import type { PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/web' async fetchCredentials(): Promise { @@ -668,157 +684,31 @@ subscription.unsubscribe(); - Default streams: server may configure streams as default — these subscribe automatically without a client call - TTL eviction: after TTL expires with no active subscriber, the stream's data may be removed from the local DB -## Raw Tables - -Raw tables let PowerSync sync data directly into native SQLite tables you define, instead of storing data as JSON in `ps_data__
` and exposing it via views. This gives full SQLite control and better query performance. See [Raw Tables](https://docs.powersync.com/usage/use-case-examples/raw-tables.md) for more information. - -Requires: The Rust sync client (now the default). Will not work with the legacy JavaScript client. - -Status: Experimental — not covered by semver stability guarantees. - -### When to Use Raw Tables - -- Complex queries that benefit from native column types (e.g. `SUM`, `GROUP BY` on typed columns) -- Tables with many rows where JSON extraction overhead is significant -- Need for SQLite constraints (foreign keys, `NOT NULL`, `GENERATED` columns) -- Custom indexes on expressions or generated columns - -### Defining a Raw Table - -```ts -import { Schema, RawTable } from '@powersync/common'; - -const schema = new Schema({ - // Regular PowerSync-managed tables here -}); - -schema.withRawTables({ - // The key name ('todo_lists') matches the table name in the backend database - // as sent by the PowerSync service — NOT necessarily the local SQLite table name - todo_lists: { - put: { - sql: 'INSERT OR REPLACE INTO todo_lists (id, created_by, title, content) VALUES (?, ?, ?, ?)', - params: ['Id', { Column: 'created_by' }, { Column: 'title' }, { Column: 'content' }] - }, - delete: { - sql: 'DELETE FROM todo_lists WHERE id = ?', - params: ['Id'] - } - } -}); -``` - -Parameter types: -- `'Id'` — replaced with the object ID from the sync service -- `{ Column: 'fieldName' }` — replaced with the value of that column from the synced row data -- For `delete` statements, only `'Id'` is supported - -You must also create the actual SQLite table separately (e.g. in app init): - -```ts -await db.execute(` - CREATE TABLE IF NOT EXISTS todo_lists ( - id TEXT NOT NULL PRIMARY KEY, - created_by TEXT NOT NULL, - title TEXT NOT NULL, - content TEXT - ) STRICT -`); -``` - -### Triggers for Local Writes - -Raw tables require manual triggers to capture local writes into PowerSync's upload queue (`powersync_crud` virtual table): - -```sql -CREATE TRIGGER todo_lists_insert - AFTER INSERT ON todo_lists FOR EACH ROW - BEGIN - INSERT INTO powersync_crud (op, id, type, data) - VALUES ('PUT', NEW.id, 'todo_lists', json_object( - 'created_by', NEW.created_by, - 'title', NEW.title, - 'content', NEW.content - )); - END; - -CREATE TRIGGER todo_lists_update - AFTER UPDATE ON todo_lists FOR EACH ROW - BEGIN - SELECT CASE - WHEN (OLD.id != NEW.id) THEN RAISE(FAIL, 'Cannot update id') - END; - INSERT INTO powersync_crud (op, id, type, data) - VALUES ('PATCH', NEW.id, 'todo_lists', json_object( - 'created_by', NEW.created_by, - 'title', NEW.title, - 'content', NEW.content - )); - END; - -CREATE TRIGGER todo_lists_delete - AFTER DELETE ON todo_lists FOR EACH ROW - BEGIN - INSERT INTO powersync_crud (op, id, type) - VALUES ('DELETE', OLD.id, 'todo_lists'); - END; -``` - -The `powersync_crud` virtual table fields: -- `op` — `'PUT'`, `'PATCH'`, or `'DELETE'` -- `id` — row ID -- `type` — table name (as the backend knows it) -- `data` — JSON object of column values (omit for DELETE) -- `old_values` — optional previous values for conflict resolution -- `metadata` — optional metadata string +## ORM & Raw Tables -### Migrating from ps_untyped +These advanced topics are in separate files — load only when needed: -If PowerSync has already synced data for a table before you added it as a raw table, it's stored in `ps_untyped`. After creating the raw table and defining it in the schema, run: +| Topic | File | Load when… | +|-------|------|-----------| +| Drizzle / Kysely ORM | `references/sdks/powersync-js-orm.md` | Using Drizzle or Kysely for type-safe queries | +| Raw Tables | `references/raw-tables.md` | Need native SQLite tables (SDK-agnostic — JS, Dart, Kotlin, Swift, Rust) | -```sql -INSERT INTO my_table (id, col1, col2) - SELECT id, data ->> 'col1', data ->> 'col2' - FROM ps_untyped WHERE type = 'my_table'; -DELETE FROM ps_untyped WHERE type = 'my_table'; -``` - -Not needed if the raw table definition was present from the very first `connect()` call. +## JS Internals -### Raw Table Caveats +> Only needed when debugging QueryStore eviction, investigating sync client implementations, or working with internal op types. -- Rust client only — the JavaScript sync client logs a warning and ignores raw tables -- No automatic column migration — adding columns requires deleting all data and resyncing, or a manual workaround -- Foreign keys — must use `DEFERRABLE INITIALLY DEFERRED`; enable with `PRAGMA foreign_keys = ON`; avoid FK references from high-priority to lower-priority raw tables (priorities sync in separate transactions) -- `disconnectAndClear()` won't clear raw tables by default — add a `clear` statement to `RawTable` if needed -- The `name` property matches the backend table name, not the local SQLite table name — `put`/`delete` can target any local table +### Sync Client Implementations -## Drizzle ORM Integration +The Rust-based sync client is now the default — no config needed. The legacy JS client (`SyncClientImplementation.JAVASCRIPT`) is deprecated. Do not downgrade the SDK after using the Rust client — older JS client versions can't read the Rust format. -See [Drizzle ORM Setup](https://docs.powersync.com/client-sdks/orms/javascript-web/drizzle.md) for full setup instructions. +### QueryStore -```ts -import { drizzle } from '@powersync/drizzle-driver'; -import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; -import { eq } from 'drizzle-orm'; - -// Define Drizzle schema -export const todos = sqliteTable('todos', { - id: text('id').primaryKey(), - description: text('description').notNull(), - completed: integer('completed').notNull().default(0), - listId: text('list_id') -}); +`useSuspenseQuery` uses a `QueryStore` (one per `PowerSyncDatabase`, stored in a `WeakMap`). Caches `WatchedQuery` instances keyed by `"${sql} -- ${JSON.stringify(params)} -- ${JSON.stringify(options)}"`. Evicted when listener count reaches 0. `useSuspenseQuery` and `useQuery` with the same SQL/params/options share the same underlying `WatchedQuery`. -// Create Drizzle instance -const drizzleDb = drizzle(db); +### Op Types (Internal Sync vs CRUD) -// Type-safe queries -const activeTodos = await drizzleDb - .select() - .from(todos) - .where(eq(todos.completed, 0)); -``` +Internal bucket ops (`OpTypeEnum`): `CLEAR=1`, `MOVE=2`, `PUT=3`, `REMOVE=4` — sync protocol only, not exposed to userland. +CRUD upload ops (`UpdateType`): `PUT`, `PATCH`, `DELETE` — what you see in `uploadData`. Don't confuse sync-level `REMOVE` with CRUD-level `DELETE`. ## Debugging @@ -925,58 +815,6 @@ console.log(db.currentStatus); // { connected, connecting, lastSyncedAt, hasSynced, isSyncing, downloadProgress } ``` -## Internals - -> Load this section only when debugging QueryStore eviction behaviour, investigating sync client implementation differences, or working with internal op types. Not needed for typical integration, setup, or feature work. - -### Sync Client Implementations - -The Rust-based sync client is now the default: - -```ts -// Default — Rust client (no config needed) -const db = new PowerSyncDatabase({ schema, database: { dbFilename: 'app.db' } }); - -// Explicit (not needed unless reverting to legacy) -import { SyncClientImplementation } from '@powersync/common'; -const db = new PowerSyncDatabase({ - schema, - database: { dbFilename: 'app.db' }, - sync: { implementation: SyncClientImplementation.RUST } // now default -}); -``` - -The Rust client: -- More performant — offloads sync line decoding to native extension -- Required for raw tables and partial checkpoints by priority -- Stores sync data in a slightly different format than the old JS client -- Auto-migrates from JS format on first use - -Do not downgrade the SDK after using the Rust client — older SDK versions using the JS client can't read the Rust format. - -The legacy JS client (`SyncClientImplementation.JAVASCRIPT`) is deprecated and will be removed in a future version. - -### QueryStore - -`useSuspenseQuery` uses a `QueryStore` (one per `PowerSyncDatabase` instance, stored in a `WeakMap`). The store caches `WatchedQuery` instances keyed by: - -``` -"${sql} -- ${JSON.stringify(params)} -- ${JSON.stringify(options)}" -``` - -A query is evicted (closed) when the count of `ON_DATA + ON_STATE_CHANGE + ON_ERROR` listeners reaches 0. - -Implication: `useSuspenseQuery` and `useQuery` with the same SQL/params/options share the same underlying `WatchedQuery`. If one component unmounts but another with the same query is still mounted, the query stays alive and is not re-fetched. - -### Op Types (Internal Sync vs CRUD) - -Internal bucket ops (`OpTypeEnum`) — used inside sync protocol, not exposed to userland: -- `CLEAR=1`, `MOVE=2`, `PUT=3`, `REMOVE=4` - -CRUD upload ops (`UpdateType`) — what you see in `uploadData`: -- `PUT`, `PATCH`, `DELETE` - -These are separate enumerations. Don't confuse the sync-level `REMOVE` with the CRUD-level `DELETE`. ## Common Pitfalls diff --git a/skills/powersync/references/sdks/powersync-kotlin.md b/skills/powersync/references/sdks/powersync-kotlin.md index 3f544df..1247c84 100644 --- a/skills/powersync/references/sdks/powersync-kotlin.md +++ b/skills/powersync/references/sdks/powersync-kotlin.md @@ -7,6 +7,18 @@ metadata: # PowerSync Kotlin SDK +> **Load this when** building a Kotlin app (Android, JVM, KMP) with PowerSync. + +## Table of Contents +- [Installation](#installation) +- [Quick Setup](#quick-setup) +- [Query Patterns](#query-patterns) +- [Writes and Transactions](#writes-and-transactions) +- [Compose Integration](#compose-integration) +- [Sync Status](#sync-status) +- [Sync Streams](#sync-streams) +- [Background Sync (Android)](#background-sync-android) + Best practices for building apps with the PowerSync Kotlin SDK. Supported targets: Android, JVM, iOS, macOS, watchOS, tvOS. diff --git a/skills/powersync/references/sdks/powersync-swift.md b/skills/powersync/references/sdks/powersync-swift.md index 4e9455e..4ad8628 100644 --- a/skills/powersync/references/sdks/powersync-swift.md +++ b/skills/powersync/references/sdks/powersync-swift.md @@ -7,6 +7,8 @@ metadata: # PowerSync Swift SDK +> **Load this when** building a Swift app (iOS, macOS) with PowerSync. + Best practices and guidance for building apps with the PowerSync Swift SDK. | Resource | Description | diff --git a/skills/powersync/references/supabase-auth.md b/skills/powersync/references/supabase-auth.md index de081b4..e3337f0 100644 --- a/skills/powersync/references/supabase-auth.md +++ b/skills/powersync/references/supabase-auth.md @@ -7,6 +7,16 @@ metadata: # PowerSync + Supabase Auth +> **Load this when** using Supabase as the backend — covers database publication setup, JWT signing keys, fetchCredentials(), uploadData error handling, and Cloud/self-hosted auth config. + +## Table of Contents +- [Supabase Database Setup](#supabase-database-setup) +- [JWT Signing Key Types](#jwt-signing-key-types) +- [PowerSync Cloud Setup](#powersync-cloud-setup) +- [Self-Hosted Config](#self-hosted-serviceyaml-config) +- [fetchCredentials()](#fetchcredentials--client-implementation) +- [Troubleshooting](#troubleshooting) + PowerSync verifies Supabase JWTs directly when connected to a Supabase-hosted Postgres database. This file covers everything needed to configure authentication end-to-end. ## Supabase Database Setup @@ -162,10 +172,18 @@ client_auth: ## `fetchCredentials()` — Client Implementation +**Prerequisite:** `fetchCredentials()` requires an active Supabase auth session. PowerSync calls it automatically whenever a token is needed, but if no session exists (user not signed in), it will throw and sync will not start. **You must sign the user in before calling `db.connect()`.** + +- If your app requires explicit sign-in (email/password, OAuth, magic link), connect PowerSync only after the sign-in completes. +- If anonymous access is acceptable, use the anonymous sign-in pattern below. +- If anonymous auth is disabled on your Supabase project, there is no silent fallback — the agent must gate `db.connect()` behind an explicit auth flow. + `fetchCredentials()` in your backend connector should return the Supabase session JWT. The examples below use the JS Supabase client; equivalent patterns exist for [Dart](https://github.com/powersync-ja/powersync.dart/blob/9ef224175c8969f5602c140bcec6dd8296c31260/demos/supabase-todolist/lib/powersync.dart#L38) and [Kotlin](https://github.com/powersync-ja/powersync-kotlin/blob/main/connectors/supabase/src/commonMain/kotlin/com/powersync/connector/supabase/SupabaseConnector.kt). ### Standard Supabase Auth (JS/TS) +Use this when users sign in explicitly (email, OAuth, magic link). Call `db.connect(connector)` only after `supabase.auth.signIn*` succeeds. + ```ts import { createClient } from '@supabase/supabase-js'; import type { PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/web'; // or @powersync/react-native @@ -188,13 +206,14 @@ export const connector: PowerSyncBackendConnector = { ### Anonymous Sign-In (JS/TS) +Use this when you want sync to work without an explicit sign-in step. Requires **anonymous sign-ins to be enabled** in Supabase (Dashboard → Authentication → Providers → Anonymous). If disabled, `signInAnonymously()` returns an error and sync fails silently. + ```ts async fetchCredentials(): Promise { - // Sign in anonymously if no session exists let { data: { session } } = await supabase.auth.getSession(); if (!session) { const { data, error } = await supabase.auth.signInAnonymously(); - if (error) throw error; + if (error) throw error; // Will throw if anonymous auth is disabled session = data.session!; } return { @@ -209,10 +228,20 @@ async fetchCredentials(): Promise { ### `uploadData()` — Writing Changes Back to Supabase -For Supabase backends, `uploadData` writes client-side changes directly to Supabase using the Supabase JS client. **`transaction.complete()` is mandatory** — without it the upload queue stalls permanently. +For Supabase backends, `uploadData` writes client-side changes directly to Supabase. **`transaction.complete()` is mandatory** — without it the upload queue stalls permanently. + +#### Error handling strategy + +| Error type | What to do | Why | +|-----------|-----------|-----| +| Network / 5xx (transient) | `throw error` — do not call `transaction.complete()` | PowerSync retries with backoff | +| 4xx / RLS violation (permanent) | Call `transaction.complete()`, log the error | 4xx blocks the queue forever; better to skip and log than halt all future writes | +| Validation error | Call `transaction.complete()`, surface via a synced error table | Data errors are permanent; retrying won't fix them | + +The Supabase JS client returns errors as `{ error: PostgrestError }` rather than throwing HTTP status codes — check `error.code` or `error.message` to distinguish permanent failures (constraint violations, RLS denials) from transient ones. Supabase RLS errors return `{ code: '42501' }` (PostgreSQL insufficient_privilege). ```ts -import type { AbstractPowerSyncDatabase, PowerSyncBackendConnector, CrudEntry, UpdateType } from '@powersync/web'; +import type { AbstractPowerSyncDatabase, PowerSyncBackendConnector, UpdateType } from '@powersync/web'; export const connector: PowerSyncBackendConnector = { async fetchCredentials() { /* ... see above ... */ }, @@ -224,29 +253,34 @@ export const connector: PowerSyncBackendConnector = { try { for (const op of transaction.crud) { const { op: opType, table, opData, id } = op; + let result: { error: any }; if (opType === UpdateType.PUT) { - const { error } = await supabase.from(table).upsert({ ...opData, id }); - if (error) throw error; + result = await supabase.from(table).upsert({ ...opData, id }); } else if (opType === UpdateType.PATCH) { - const { error } = await supabase.from(table).update(opData).eq('id', id); - if (error) throw error; - } else if (opType === UpdateType.DELETE) { - const { error } = await supabase.from(table).delete().eq('id', id); - if (error) throw error; + result = await supabase.from(table).update(opData).eq('id', id); + } else { + result = await supabase.from(table).delete().eq('id', id); } + if (result.error) throw result.error; + } + await transaction.complete(); // REQUIRED — advances the queue + } catch (error: any) { + // Permanent failures (RLS violation, constraint error, 4xx-equivalent): + // complete the transaction so the queue can advance. Log for debugging. + const isPermanent = error?.code === '42501' || error?.status === 400; + if (isPermanent) { + console.error('Permanent upload error, skipping:', error); + await transaction.complete(); + return; } - await transaction.complete(); // REQUIRED — clears the queue entry - } catch (error) { - // For 4xx errors (permanent failures), complete the transaction to avoid - // blocking the queue. For 5xx/network errors, throw to trigger a retry. - console.error('Upload error', error); + // Transient failures: throw so PowerSync retries with backoff. throw error; } } }; ``` -**Important:** RLS policies on your Supabase tables must allow the authenticated user to write their own rows. If `uploadData` consistently gets 4xx errors, the queue stalls — call `transaction.complete()` and log the error rather than retrying forever. +**Important:** RLS policies on your Supabase tables must allow the authenticated user to write their own rows. Ensure `INSERT`/`UPDATE`/`DELETE` policies exist — `SELECT`-only policies silently block all writes. ### Getting the PowerSync Instance URL diff --git a/skills/powersync/references/sync-config.md b/skills/powersync/references/sync-config.md index 4fcc17f..0c8ef36 100644 --- a/skills/powersync/references/sync-config.md +++ b/skills/powersync/references/sync-config.md @@ -7,6 +7,12 @@ metadata: # Sync Config +> **Load this when** writing or modifying sync configuration — Sync Streams (new) or Sync Rules (legacy). Required for every PowerSync project. + +## Table of Contents +**Sync Streams (new):** [Requirements](#requirements) · [File Format](#sync-configyaml-file-format) · [Structure](#structure) · [Stream Options](#stream-options) · [Common Patterns](#common-patterns) · [Query Parameters](#query-parameters) · [CTEs](#common-table-expressions-ctes) · [Migration](#migration) · [Client Usage](#client-usage) · [Advanced Topics](#advanced-topics) +**Sync Rules (legacy):** [Structure](#structure-1) · [Parameter Queries](#parameter-queries) · [Data Queries](#data-queries) · [Supported SQL](#supported-sql-features) · [Common Patterns](#common-patterns-1) + Expert guidance on Sync Config. Sync config is divided into two sections: 1. Sync Streams (new, default) - The latest implementation of Sync Config. New apps should use Sync Streams by default. Prioritize Sync Streams above Sync Rules. 2. Sync Rules (legacy) - The first implementation of Sync Config. New apps should not use Sync Rules, prioritize Sync Streams over Sync Rules. From 878a6f4cdd012806c50a74da9eac53780cc6acd4 Mon Sep 17 00:00:00 2001 From: bean1352 Date: Tue, 7 Apr 2026 14:14:01 +0200 Subject: [PATCH 8/9] Include a note on restarting the service after changes to `service.yaml`. --- skills/powersync/references/supabase-auth.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skills/powersync/references/supabase-auth.md b/skills/powersync/references/supabase-auth.md index e3337f0..a411fdf 100644 --- a/skills/powersync/references/supabase-auth.md +++ b/skills/powersync/references/supabase-auth.md @@ -339,6 +339,8 @@ PowerSync cannot verify the JWT signature. Check the error logs for `Known keys` | Wrong JWT secret | HS256 verification fails | For legacy HS256 keys, verify the secret matches Supabase → Project Settings → JWT. | | `block_local_jwks` blocking JWKS fetch | JWKS URI resolves to private IP, keys never fetched | Set `block_local_jwks: false` for local development. | +After any `service.yaml` auth change, restart the service to pick it up: `powersync docker reset` (self-hosted) or `powersync deploy service-config` (Cloud). + ### `PSYNC_S2105` — JWT payload is missing a required claim "aud" Using manual JWKS config without specifying an audience. Add `authenticated` to the audience list (Cloud dashboard or `audience: [authenticated]` in `service.yaml`). From 53590f788d2e677642be041a430e59f016455ce1 Mon Sep 17 00:00:00 2001 From: bean1352 Date: Wed, 8 Apr 2026 09:37:16 +0200 Subject: [PATCH 9/9] Always install latest PowerSync dependencies --- skills/powersync/AGENTS.md | 11 ++++++++++ .../references/sdks/powersync-js-node.md | 6 ++--- .../references/sdks/powersync-js-orm.md | 4 ++-- .../sdks/powersync-js-react-native.md | 8 +++---- .../references/sdks/powersync-js-react.md | 2 +- .../references/sdks/powersync-js-tanstack.md | 6 ++--- .../references/sdks/powersync-js-vue.md | 10 ++++----- .../powersync/references/sdks/powersync-js.md | 22 +++++++++---------- 8 files changed, 40 insertions(+), 29 deletions(-) diff --git a/skills/powersync/AGENTS.md b/skills/powersync/AGENTS.md index 17a2196..98d3333 100644 --- a/skills/powersync/AGENTS.md +++ b/skills/powersync/AGENTS.md @@ -40,6 +40,17 @@ When the task is to add PowerSync to an app, follow this sequence in order: Do not start client-side debugging while the PowerSync service is still unconfigured. If the UI is stuck on `Syncing...`, the default diagnosis is incomplete backend setup, not a frontend bug. +## Install Latest Dependencies + +Always install PowerSync packages with `@latest` to get critical fixes and the most current API: + +```bash +npm install @powersync/web@latest # or react-native, node, etc. +npm install @journeyapps/wa-sqlite@latest +``` + +Never omit `@latest` for `@powersync/*` and `@journeyapps/*` packages. These packages release frequently and older cached versions can be missing critical fixes or new APIs the sync config depends on. + ## Critical Footguns These apply to all paths. Domain-specific pitfalls are documented in the relevant reference files — only load those when working on that domain. diff --git a/skills/powersync/references/sdks/powersync-js-node.md b/skills/powersync/references/sdks/powersync-js-node.md index d967e43..e728e95 100644 --- a/skills/powersync/references/sdks/powersync-js-node.md +++ b/skills/powersync/references/sdks/powersync-js-node.md @@ -21,7 +21,7 @@ Node.js-specific integration for the PowerSync JavaScript SDK. Use this referenc ### 1. Install ```bash -npm install @powersync/node +npm install @powersync/node@latest npm install better-sqlite3 # required peer dependency ``` @@ -125,7 +125,7 @@ Electron App The renderer process is a full browser environment. Set it up exactly like any other web app: ```bash -npm install @powersync/web @journeyapps/wa-sqlite @powersync/react +npm install @powersync/web@latest @journeyapps/wa-sqlite@latest @powersync/react@latest ``` Use `PowerSyncContext.Provider` and `useQuery`/`useStatus` hooks as documented in `references/sdks/powersync-js-react.md`. The Web-Specific Options section in `references/sdks/powersync-js.md` (VFS options, multi-tab, `debugMode`) also applies to the renderer process. @@ -135,7 +135,7 @@ Use `PowerSyncContext.Provider` and `useQuery`/`useStatus` hooks as documented i The main process runs Node.js with no DOM. Use `@powersync/node`: ```bash -npm install @powersync/node better-sqlite3 +npm install @powersync/node@latest better-sqlite3 ``` ```ts diff --git a/skills/powersync/references/sdks/powersync-js-orm.md b/skills/powersync/references/sdks/powersync-js-orm.md index 696f090..8eff9be 100644 --- a/skills/powersync/references/sdks/powersync-js-orm.md +++ b/skills/powersync/references/sdks/powersync-js-orm.md @@ -19,7 +19,7 @@ PowerSync provides official drivers for both Drizzle and Kysely. These let you w ## Drizzle ```bash -npm install @powersync/drizzle-driver drizzle-orm +npm install @powersync/drizzle-driver@latest drizzle-orm ``` ```ts @@ -57,7 +57,7 @@ const { data } = useQuery(query); ## Kysely ```bash -npm install @powersync/kysely-driver kysely +npm install @powersync/kysely-driver@latest kysely ``` ```ts diff --git a/skills/powersync/references/sdks/powersync-js-react-native.md b/skills/powersync/references/sdks/powersync-js-react-native.md index 14473da..aa556db 100644 --- a/skills/powersync/references/sdks/powersync-js-react-native.md +++ b/skills/powersync/references/sdks/powersync-js-react-native.md @@ -24,17 +24,17 @@ The React hooks API (`useQuery`, `useStatus`, `usePowerSync`, `useSuspenseQuery` ### Standard React Native (Recommended) ```bash -npm install @powersync/react-native +npm install @powersync/react-native@latest ``` Then install a native SQLite adapter (required peer dependency): ```bash # OP-SQLite — recommended: built-in encryption, React Native New Architecture support -npm install @powersync/op-sqlite +npm install @powersync/op-sqlite@latest # OR: React Native Quick SQLite — original adapter -npm install @journeyapps/react-native-quick-sqlite +npm install @journeyapps/react-native-quick-sqlite@latest ``` After installing native dependencies, rebuild your native app: @@ -110,7 +110,7 @@ Expo Go is a sandbox that does not support native modules. To run PowerSync in E ### Install ```bash -npm install @powersync/react-native @powersync/adapter-sql-js +npm install @powersync/react-native@latest @powersync/adapter-sql-js@latest ``` ### Usage diff --git a/skills/powersync/references/sdks/powersync-js-react.md b/skills/powersync/references/sdks/powersync-js-react.md index c01f527..0f866e2 100644 --- a/skills/powersync/references/sdks/powersync-js-react.md +++ b/skills/powersync/references/sdks/powersync-js-react.md @@ -134,7 +134,7 @@ PowerSync is tailored for client-side applications. Next.js evaluates code in a ### Install ```bash -npm install @powersync/web @journeyapps/wa-sqlite @powersync/react +npm install @powersync/web@latest @journeyapps/wa-sqlite@latest @powersync/react@latest ``` ### Copy Worker Assets (Turbopack) diff --git a/skills/powersync/references/sdks/powersync-js-tanstack.md b/skills/powersync/references/sdks/powersync-js-tanstack.md index 092110c..983a2a0 100644 --- a/skills/powersync/references/sdks/powersync-js-tanstack.md +++ b/skills/powersync/references/sdks/powersync-js-tanstack.md @@ -37,7 +37,7 @@ Use `@powersync/react` hooks (from `references/sdks/powersync-js-react.md`) when ### Install ```bash -npm install @powersync/tanstack-react-query +npm install @powersync/tanstack-react-query@latest ``` TanStack Query also requires its core peer: @@ -109,10 +109,10 @@ Use plain PowerSync (`.watch()` / `usePowerSyncQuery`) when: ```bash # Web -npm install @tanstack/powersync-db-collection @powersync/web @journeyapps/wa-sqlite +npm install @tanstack/powersync-db-collection@latest @powersync/web@latest @journeyapps/wa-sqlite@latest # React Native -npm install @tanstack/powersync-db-collection @powersync/react-native +npm install @tanstack/powersync-db-collection@latest @powersync/react-native@latest ``` Also install a TanStack DB framework adapter for your UI framework: diff --git a/skills/powersync/references/sdks/powersync-js-vue.md b/skills/powersync/references/sdks/powersync-js-vue.md index a098473..1588437 100644 --- a/skills/powersync/references/sdks/powersync-js-vue.md +++ b/skills/powersync/references/sdks/powersync-js-vue.md @@ -28,10 +28,10 @@ Vue-specific integration for the PowerSync JavaScript SDK. Use this reference al ```bash # npm (v7+ installs peer dependencies automatically) -npm install @powersync/vue +npm install @powersync/vue@latest # pnpm (must install peer dependencies explicitly) -pnpm add @powersync/vue @powersync/web @journeyapps/wa-sqlite +pnpm add @powersync/vue@latest @powersync/web@latest @journeyapps/wa-sqlite@latest ``` ### 2. Plugin Setup @@ -177,10 +177,10 @@ PowerSync is tailored for client-side applications. Nuxt evaluates plugins serve ```bash # npm (v7+ installs peer dependencies automatically) -npm install @powersync/nuxt +npm install @powersync/nuxt@latest # pnpm (must install peer dependencies explicitly) -pnpm add @powersync/nuxt @powersync/vue @powersync/web +pnpm add @powersync/nuxt@latest @powersync/vue@latest @powersync/web@latest ``` ### 2. `nuxt.config.ts` @@ -253,7 +253,7 @@ The module optionally exposes a `usePowerSyncKysely()` composable for type-safe Install the driver: ```bash -npm install @powersync/kysely-driver +npm install @powersync/kysely-driver@latest ``` Enable it in `nuxt.config.ts`: diff --git a/skills/powersync/references/sdks/powersync-js.md b/skills/powersync/references/sdks/powersync-js.md index 83b6860..e15c505 100644 --- a/skills/powersync/references/sdks/powersync-js.md +++ b/skills/powersync/references/sdks/powersync-js.md @@ -70,34 +70,34 @@ Framework-specific files (load alongside this file): ```bash # Web -npm install @powersync/web -npm install @journeyapps/wa-sqlite # Needed (peer-dependency) +npm install @powersync/web@latest +npm install @journeyapps/wa-sqlite@latest # Needed (peer-dependency) # React Native -npm install @powersync/react-native -npm install @powersync/powersync-op-sqlite # Needed (peer-dependency) +npm install @powersync/react-native@latest +npm install @powersync/powersync-op-sqlite@latest # Needed (peer-dependency) # Node.js -npm install @powersync/node +npm install @powersync/node@latest npm install better-sqlite3 # Needed (peer-dependency) # React integration -npm install @powersync/react +npm install @powersync/react@latest # Vue -npm install @powersync/vue +npm install @powersync/vue@latest # Nuxt (includes @powersync/vue — npm v7+ installs peers automatically) -npm install @powersync/nuxt +npm install @powersync/nuxt@latest # TanStack Query (React) -npm install @powersync/tanstack-react-query +npm install @powersync/tanstack-react-query@latest # TanStack DB -npm install @tanstack/powersync-db-collection +npm install @tanstack/powersync-db-collection@latest ``` -Always install packages by running these commands rather than writing versions into `package.json` manually. Using `"latest"` as a version string in `package.json` is incorrect — it bypasses the lockfile and can pull in breaking changes at any install. +Always install packages using `@latest` as shown above — PowerSync releases frequently and older cached versions can be missing critical fixes. Do not write version strings into `package.json` manually. See the framework-specific files for full setup instructions per target.