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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions skills/powersync/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ If a sentence is ambiguous, default to the operator interpretation.

| Do | Don't |
|----|-----|
| **Ask the operator** before doing anything: (1) **Cloud or self-hosted**, (2) **which database** (Supabase, Postgres, MongoDB, MySQL, MSSQL), and (3) **only if not Supabase**, whether they have a backend API or need to build one. Use the **PowerSync CLI** by default for all operations. | Assume any answer, pick a default (e.g. Supabase, Postgres, self-hosted Docker), skip a question, or ask about a backend API when the operator already chose Supabase (Supabase *is* the backend). Do not hand-write config files or use the dashboard unless the operator explicitly says they cannot use the CLI. |
| **Ask the operator** before doing anything: (1) **Cloud or self-hosted**, (2) **which database** (Supabase, Postgres, MongoDB, MySQL, MSSQL, or Convex), and (3) **only if not Supabase**, whether they have a backend API or need to build one. Use the **PowerSync CLI** by default for all operations. | Assume any answer, pick a default (e.g. Supabase, Postgres, self-hosted Docker), skip a question, or ask about a backend API when the operator already chose Supabase (Supabase *is* the backend). Do not hand-write config files or use the dashboard unless the operator explicitly says they cannot use the CLI. |
| 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 operator 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 operator confirmation |
| Complete **backend readiness** (deployed sync config, auth, publication) **before** app code | Start React/client integration while sync is still unconfigured |
Expand Down Expand Up @@ -72,8 +72,9 @@ No persistent memory in your harness? Ask the operator at session start and keep
When the task is to add PowerSync to an app, follow this sequence:

1. **Platform.** Cloud or self-hosted?
2. **Backend.** If unspecified, ask (Supabase, custom Postgres, MongoDB, MySQL, MSSQL). Do not assume Supabase. Then load:
2. **Backend.** If unspecified, ask (Supabase, custom Postgres, MongoDB, MySQL, MSSQL, or Convex). Do not assume Supabase. Then load:
- Supabase → `references/onboarding-supabase.md`
- Convex → `references/powersync-service.md` § "Convex Quick Start"; writes go to Convex mutations from `uploadData()` — no custom REST API needed for the write path.
- Anything else → `references/onboarding-custom.md` (must create a backend API with `uploadData`, token, and JWKS endpoints — do not skip this)
3. **Supabase online or local?** If unclear from project/env, ask before choosing connection strings, auth config, or references.
4. Collect required inputs before coding (see "Required Inputs Below").
Expand Down
2 changes: 2 additions & 0 deletions skills/powersync/references/custom-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ 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.

> **Convex exception:** If the source database is Convex, `uploadData()` calls Convex mutations directly — no intermediate REST API is required for the write path. If using Convex Auth tokens for PowerSync client authentication, configure `client_auth.audience: [convex]` in `service.yaml`. See `references/powersync-service.md` § "Convex Quick Start".

## 1. Custom JWT Auth

PowerSync verifies JWTs from client apps. Without Supabase, you must generate and serve your own JWTs and JWKS.
Expand Down
61 changes: 60 additions & 1 deletion skills/powersync/references/powersync-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: powersync-service
description: PowerSync Service configuration — self-hosting, Docker, Kubernetes, Helm, source database setup, bucket storage, authentication, and PowerSync Cloud
metadata:
tags: service, self-hosted, docker, postgresql, mongodb, mysql, mssql, authentication, jwt, replication, configuration, private-endpoints, privatelink, vpc, aws, kubernetes, helm, eks
tags: service, self-hosted, docker, postgresql, mongodb, mysql, mssql, convex, authentication, jwt, replication, configuration, private-endpoints, privatelink, vpc, aws, kubernetes, helm, eks
---

# PowerSync Service
Expand Down Expand Up @@ -401,6 +401,65 @@ EXEC sys.sp_cdc_enable_table
EXEC sys.sp_cdc_change_job @job_type = N'capture', @pollinginterval = 1;
```

### Convex Quick Start

> **Experimental.** The Convex replicator is experimental; APIs and behavior may change.

PowerSync replicates from Convex via the Convex Streaming Export API (polling `document_deltas`), not CDC.

**Before connecting PowerSync**, add the `powersync_checkpoints` table and `createCheckpoint` mutation to your Convex deployment. PowerSync calls this mutation to advance the replication cursor:

```typescript
// convex/schema.ts — add to your existing defineSchema
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';

export default defineSchema({
// ... your other tables
powersync_checkpoints: defineTable({
last_updated: v.float64()
})
});
```

```typescript
// convex/powersync_checkpoints.ts
import { mutation } from './_generated/server';

export const createCheckpoint = mutation({
args: {},
handler: async (ctx) => {
const existing = await ctx.db.query('powersync_checkpoints').first();
if (existing) {
await ctx.db.patch(existing._id, { last_updated: Date.now() });
} else {
await ctx.db.insert('powersync_checkpoints', { last_updated: Date.now() });
}
}
});
```

**Deploy key:** In the Convex Dashboard → **Settings** → **General**, generate a deploy key with **Custom permissions** that include `deployment:data:view`. The **Deploy only** option does not provide sufficient access for replication.

**Service YAML (self-hosted):**

```yaml
replication:
connections:
- type: convex
deployment_url: https://<your-deployment>.convex.cloud
deploy_key: <your-deploy-key>
# Optional:
# polling_interval_ms: 1000 # default; lower reduces replication lag
# request_timeout_ms: 60000 # default
```

**Client ID mapping:** Convex generates `_id` server-side; clients need a stable local ID before a write is uploaded. Use a client-generated UUID column named `id` in your Convex schema, and map relationship foreign keys via `<table>_uuid` columns rather than the Convex `_id`. In Sync Streams, select `uuid AS id`. See [Sync Streams: Convex with ID Mapping](https://docs.powersync.com/sync/streams/examples.md#convex-with-id-mapping) for a complete example.

**Replication latency:** Convex replication is polling-based (default 1000ms interval). Lowering `polling_interval_ms` reduces lag but increases Convex API requests.

**Dropping tables:** Deleting a Convex table in the dashboard does not emit per-document delete rows. If decommissioning a table, use **Clear Table** in the Convex dashboard (or delete documents via mutations) first, then delete the table after those removals have replicated.

## App Backend

PowerSync does not write client-side changes stored in the SQLite database back to the connected source database. Client applications are required to implement the `uploadData` function which should call a backend API to persist the local SQLite changes to the source database.
Expand Down
35 changes: 34 additions & 1 deletion skills/powersync/references/sync-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: sync-config
description: PowerSync Sync Config — Sync Streams (new), Sync Rules (legacy), parameters, CTEs, common patterns, and migration guidance
metadata:
tags: sync-streams, sync-rules, sync-config, yaml, buckets, parameters, cte, migration
tags: sync-streams, sync-rules, sync-config, yaml, buckets, parameters, cte, migration, convex
---

# Sync Config
Expand Down Expand Up @@ -426,6 +426,39 @@ Reference these when the standard patterns don't cover your use case:
| [Partitioned Tables](https://docs.powersync.com/sync/advanced/partitioned-tables.md) | Sync from Postgres partitioned tables |
| [Sharded Databases](https://docs.powersync.com/sync/advanced/sharded-databases.md) | Source data from multiple database shards |

## Convex-Specific Patterns

If the source database is Convex, apply these adjustments when writing Sync Streams:

- **ID mapping:** Use `uuid AS id` instead of `_id AS id`. Convex generates `_id` server-side; clients need a stable local UUID before writes are uploaded. Use a client-generated UUID column as `id` and a `<table>_uuid` column for relationship joins rather than the Convex `_id` foreign key.
- **Int64 casting:** Convex `Int64` values arrive as base-10 strings (`text` in SQLite). Cast to a SQLite integer when needed: `CAST(an_int64_column AS INTEGER) AS an_int64_column`.
- **Convex Auth user ID extraction:** If using Convex Auth JWTs, the `sub` claim has the format `[32-character user ID]|[session ID]`. Extract just the user ID with `substring(auth.user_id(), 1, 32)`.
- **Table names:** Do not qualify Convex table names with a schema prefix. If a qualifier is required, use the default `convex` schema.

Example applying all of the above:

```yaml
config:
edition: 3

streams:
user_data:
with:
user_lists: |
SELECT uuid FROM lists
WHERE owner_id = substring(auth.user_id(), 1, 32)
auto_subscribe: true
queries:
- SELECT uuid AS id, name, owner_id FROM lists WHERE uuid IN user_lists
- |
SELECT
uuid AS id,
list_uuid,
description,
CAST(an_int64_column AS INTEGER) AS an_int64_column
FROM todos WHERE list_uuid IN user_lists
```

# Sync Rules (Legacy, use Sync Streams for new applications)

Sync rules define how data is partitioned into buckets and distributed to clients. This is considered legacy, however will still be supported. For the best experience use [sync-streams](#sync-streams).
Expand Down
Loading