# Technical documentation — datafy_powersync

Evidence date: 2026-10-17. Revision: `main` `0aa51f98ff6068260c0a96ccac1d1f47310e769e` (2025-02-25, Jaco Brits `<jaco@netstart.co.za>`, "added abolute paths").

Remote inspected: `git@bitbucket.org:overdrive/datafy_powersync.git`. Default branch `main`. 26 commits. No tags. No other branches.

`README.md` is empty. Operational detail is in Compose and YAML.

## Architecture

Three stacks share one image and, in places, one sync-rule text. They do not include each other.

```text
datafy_powersync/                         main @ 0aa51f9
├── docker-compose.yaml                   root stack (current filename)
├── ps-mongo.yaml                         included by the root stack
├── example.env
├── config.yaml                           standalone older service config
├── config/
│   ├── powersync.yaml                    !env service config
│   └── sync_rules.yaml
├── services/
│   ├── powersync.yaml                    extended by demos
│   ├── mongo.yaml
│   └── postgres.yaml
├── v2/
│   ├── docker-compose.yaml               absolute /home/ubuntu/... mounts
│   ├── config.yaml
│   ├── powersync.yaml
│   └── config/
│       ├── config.yaml
│       └── powersync.yaml
├── docker-compose_old.yaml
├── old.txt
├── Mackup.yml                            Compose, despite the name
└── demos/
    ├── nodejs/                           todo client + setup.sql
    └── supabase/                         local Supabase notes + Datafy sync copy
```

Runtime shape the root file declares:

```text
example.env
    → PS_PG_URI / PS_MONGO_URI / PS_PORT
        → powersync (unified) :${PS_PORT}
            → Mongo mongo:27017 / powersync_datafy?replicaSet=rs0
            → Postgres at PG_DATABASE_HOST (not started by this file)
```

`v2/` declares a different shape: RDS Postgres as the replication source, and a Postgres container as bucket storage. Mongo in that file is commented out.

## Frameworks and languages

| Piece | Technology | Evidence |
|---|---|---|
| Orchestration | Docker Compose, `include` and `extends`. Comment requires Compose > 2.20.3 | Root compose, demos, `Mackup.yml` |
| Sync service | `journeyapps/powersync-service:latest` | Every PowerSync service |
| Bucket store (root, demos) | `mongo:7.0` | `ps-mongo.yaml`, `services/mongo.yaml` |
| Postgres | `postgres:latest` | `services/postgres.yaml`, `v2/docker-compose.yaml` |
| Demo UI | React 18, Vite 5, MUI 5, React Router 6, TypeScript 5 | `demos/nodejs/demo-app/package.json` |
| PowerSync client | `@powersync/web` `^1.12.1`, `@powersync/react` `^1.5.1`, `wa-sqlite` | same `package.json` |
| Demo app package | `@powersync/react-custom-auth-todolist` `0.2.3`, private | same file |
| Node | Image `node:20`. `.nvmrc` is `v20.9.0` | Dockerfiles, `.nvmrc` |
| Package manager | pnpm. Node demo image installs `pnpm@9` and `pnpm install --frozen-lockfile` | `demos/nodejs/demo-app/Dockerfile` |
| Supabase JS | `@supabase/supabase-js` `^2.39.7` is a dependency of the Node demo. `DemoConnector.ts` does not import it | `package.json` versus `DemoConnector.ts` |

No PHP, .NET, Python, or Terraform. No application server of Overdrive's own.

## Configuration reference

### Root `docker-compose.yaml`

| Key | Value |
|---|---|
| `include` | `ps-mongo.yaml` |
| Image / command | `journeyapps/powersync-service:latest`, `["start", "-r", "unified"]` |
| `depends_on` | `mongo` only, no health condition |
| Volumes | `./powersync.yaml:/app/powersync.yaml:ro` (source file not in git), `./config:/app/config:ro`, `./config:/config:ro` |
| `POWERSYNC_CONFIG_PATH` | Commented out |
| `PS_PG_URI` | `postgres://${PG_DATABASE_USER}:${PG_DATABASE_PASSWORD}@{PG_DATABASE_HOST}:${PG_DATABASE_PORT}/${PG_DATABASE_NAME}` |
| `PS_MONGO_URI` | `mongodb://mongo:27017/powersync_datafy?replicaSet=rs0&connectTimeoutMS=30000&socketTimeoutMS=30000` |
| `PS_PORT` | `${PS_PORT}` |
| Ports | `${PS_PORT}:${PS_PORT}` |
| `network_mode` | `"bridge"` |
| Restart / healthcheck | Not set |

`config/powersync.yaml` is the file under `./config` that uses `!env PS_PG_URI`, `!env PS_MONGO_URI`, `!env PS_PORT`, `sync_rules.path: sync_rules.yaml`, `sslmode: disable`, `supabase: true`.

The `!env` comment in that file says only variables whose names start with `PS_` are substituted. `PS_PG_URI` qualifies. `PG_DATABASE_HOST` is a Compose variable, not a `!env` variable.

### `example.env`

| Variable | Value in the file |
|---|---|
| `PG_DATABASE_PORT` | `5432` |
| `PG_DATABASE_NAME` | `postgres` |
| `PG_DATABASE_USER` | `postgres` |
| `PG_DATABASE_PASSWORD` | a local placeholder (not the RDS password) |
| `PG_DATABASE_HOST` | `postgres` |
| `PS_PORT` | `8080` |

Not defined there: `PS_DATA_SOURCE_URI`, `PS_SUPABASE_JWT_SECRET`, `DEMO_*`, `SUPABASE_*`.

### `services/powersync.yaml`

Used by `demos/nodejs/docker-compose.yaml`, `demos/supabase/docker-compose.yaml`, and `Mackup.yml` via `extends`. Not used by the root compose file.

| Key | Value |
|---|---|
| `restart` | `unless-stopped` |
| Volume | `../config:/config` (path is relative to `services/`) |
| `POWERSYNC_CONFIG_PATH` | `/config/powersync.yaml` |
| `NODE_OPTIONS` | `--max-old-space-size=1000` |
| `PS_DATA_SOURCE_URI` | `${PS_DATA_SOURCE_URI}` |
| `PS_MONGO_URI` | `mongodb://mongo:27017/powersync_datafy` (no replicaSet query) |
| Healthcheck | Node `fetch` of `http://localhost:${PS_PORT}/probes/liveness`. Interval 5s, timeout 1s, retries 15. Comment: image `0.5.8` or later |
| Ports | `${PS_PORT}:${PS_PORT}` |

### `ps-mongo.yaml` versus `services/mongo.yaml`

Both use `mongo:7.0`, `--replSet rs0 --bind_ip_all`, port `27017:27017`, volume `mongo_storage`.

| | `ps-mongo.yaml` (root include) | `services/mongo.yaml` (demos) |
|---|---|---|
| `--quiet` | Yes | No. Comment says it was removed for visibility |
| Healthcheck | None | `mongosh` ping, interval 5s, timeout 30s, retries 5 |
| Init | `sleep 10`, then initiate if `rs.status()` throws | Wait until healthy, then initiate if status is not ok |
| `depends_on` | `mongo` with no condition | `mongo` healthy |

### `services/postgres.yaml`

Service name `pg-db`. Image `postgres:latest`. Env from `PG_DATABASE_USER`, `PG_DATABASE_NAME`, `PG_DATABASE_PASSWORD`, `PG_DATABASE_PORT`. Volume `pg_data`. Command `postgres -c wal_level=logical`. Healthcheck `pg_isready`. Port `${PG_DATABASE_PORT}:${PG_DATABASE_PORT}`.

The Node demo adds `./init-scripts:/docker-entrypoint-initdb.d`.

### `v2/docker-compose.yaml`

| Key | Value |
|---|---|
| PowerSync volumes | `/home/ubuntu/datafy_powersync/v2/powersync.yaml` → `/app/powersync.yaml`, and `v2/config` → both `/app/config` and `/config` |
| `POWERSYNC_CONFIG_PATH` | Commented (`/config/config.yaml`) |
| PowerSync port | `8080:8080` |
| `depends_on` | `postgres` healthy. `mongo-rs-init` commented out |
| Postgres | user/db/password `postgres`, `PGPORT=5432`, host publish `5431:5432`, `wal_level=logical`, volume `pg_data` |
| Mongo | Commented out |

Service YAML under `v2/` (replication section is the same in all four files):

- User `supabase_admin`
- Host `datafy-supabase-databasecluster.cx6ggccgkh70.eu-west-1.rds.amazonaws.com`
- Port `5432`, database `postgres`
- `sslmode: disable`
- Password present in the URI. Omitted here on purpose. It remains in git history even if the files are edited later.
- `storage.type: postgresql`
- Storage target `localhost:5431` in `v2/config.yaml`, `v2/powersync.yaml`, and `v2/config/powersync.yaml`
- Storage target `postgres:5431` in `v2/config/config.yaml`
- Storage user and password `postgres`
- `port: 8080`
- Sync rules inlined (same four-table SQL)
- `client_auth.supabase: true`
- `telemetry` nested under `client_auth` with `disable_telemetry_sharing: false`

`old.txt` uses a different schema: `database.type: postgres`, `storage.type: postgres`, `http.port: 8080`, `auth.type: jwt` with `JWT_SECRET`, `claims_namespace: claims_`, `roles_claim: role`, `default_role: authenticated`, and `sync_rules.file: /app/config/sync-rules.yaml`. That shape is not what `config/powersync.yaml` uses. Provenance: **Implemented** as notes in `old.txt`. Nothing includes the file.

### Demo env templates

`demos/supabase/.env.template`:

| Variable | Value |
|---|---|
| `SUPABASE_URL` | `http://localhost:54321` |
| `SUPABASE_ANON_KEY` | empty |
| `PS_SUPABASE_JWT_SECRET` | empty |
| `PG_DATABASE_HOSTNAME` | `supabase_db_powersync_datafy` |
| `PG_DATABASE_NAME` / user / password | `postgres` / `postgres` / `postgres` |
| `PG_DATABASE_PORT` | `5432` (comment: this is the port on the Supabase Docker network, not the host port) |
| `PS_DATA_SOURCE_URI` | `postgres://${PG_DATABASE_USER}:${PG_DATABASE_PASSWORD}@${PG_DATABASE_HOSTNAME}:${PG_DATABASE_PORT}/${PG_DATABASE_NAME}` |
| `PS_PORT` | `8080` |

The README says Supabase's default host port is too high and that this demo uses Postgres port `5433`. The template says `5432`. Provenance: **Documented** versus **Implemented**.

`demos/nodejs/demo-app/.env.template` sets `VITE_BACKEND_URL=http://localhost:6060`, `VITE_POWERSYNC_URL=http://localhost:8080`, `VITE_CHECKPOINT_MODE=managed`. The comment tells the reader to copy `.env.local.template`. That filename is not in git.

`ps-nodejs-demo.yaml` build args are `VITE_POWERSYNC_URL=http://localhost:${PS_PORT}`, `VITE_BACKEND_URL=http://localhost:${DEMO_BACKEND_PORT}`, `VITE_CHECKPOINT_MODE=managed`. Backend `POWERSYNC_URL` is the string `powersync-dev` (comment: used as JWT audience). `JWT_ISSUER` is `powersync-dev`.

## Sync rules

File `config/sync_rules.yaml` (duplicates listed in `features.md`):

```text
global
  SELECT * FROM task
  SELECT * FROM store
  SELECT * FROM app_user
  SELECT * FROM store_user_link

user_tasks
  parameters: store_code FROM store_user_link WHERE user_id = request.user_id()
  data:       task WHERE store_code = bucket.store_code

user_stores
  parameters: same store_code query
  data:       store WHERE store_code = bucket.store_code
```

No other tables. No `active` filter. Header comment links to `https://docs.powersync.com/usage/sync-rules`.

`config/powersync.yaml` loads them with `sync_rules.path: sync_rules.yaml`. Root `config.yaml` and the `v2/` files embed the SQL under `sync_rules.content` instead.

## Demo application structure

`demos/nodejs/demo-app/src`:

| Path | Role |
|---|---|
| `app/router.tsx` | `/` → entry; `/views/todo-lists`; `/views/todo-lists/:id`; `/sql-console` |
| `app/page.tsx` | On mount, navigates to `/views/todo-lists` |
| `app/views/todo-lists/page.tsx` | List screen |
| `app/views/todo-lists/edit/page.tsx` | Edit screen |
| `app/views/sql-console/page.tsx` | SQL console |
| `library/powersync/AppSchema.ts` | Tables `lists` and `todos` |
| `library/powersync/DemoConnector.ts` | Token and upload |
| `components/providers/SystemProvider.tsx` | PowerSync provider wiring |

`Dockerfile` builds with `pnpm run build` (`tsc -b && vite build`) and runs `pnpm preview --host`. Vite's preview default port is what the compose file maps to container port `4173`. Provenance: **Implemented** `CMD` and port mapping. The Node README's port `3030` is not in the Dockerfile.

`demos/supabase/demo-client.dockerfile` clones `https://github.com/powersync-ja/powersync-js.git` at the default branch, runs `pnpm install` and `pnpm build:packages`, then builds `demos/react-supabase-todolist`. Build args `VITE_SUPABASE_URL`, `VITE_SUPABASE_ANON_KEY`, `VITE_POWERSYNC_URL` are declared and not copied into an env file in the Dockerfile. Provenance: **Implemented** ARG lines. Whether the upstream demo reads those args is **Unknown** from this repo, because the upstream project is not vendored.

`Mackup.yml` builds `dockerfile: demo-client.dockerfile` with no `context`. That dockerfile lives in `demos/supabase/`, not beside `Mackup.yml`. Provenance: **Implemented** paths. **Inferred** from Compose's default context (the compose file's directory) that the build does not find the dockerfile.

## Auth

| Mode | Where | Detail |
|---|---|---|
| Supabase flag | All service YAML | `client_auth.supabase: true` |
| Audiences | All service YAML | `powersync-dev`, `powersync` |
| JWT secret env | Supabase demo only | `supabase_jwt_secret: !env PS_SUPABASE_JWT_SECRET` |
| Demo custom JWT | Node demo backend env | Public/private key env vars, issuer `powersync-dev`. Keys are not committed. Backend code is the unpinned GitHub repo |
| Demo user id | `DemoConnector.ts` | UUID in `localStorage` |

No session store, no password reset, no captcha, no rate limit is defined in this repo. Those would be properties of the PowerSync image or the demo backend, which are not source here.

## Data

This repo does not own a schema migration tool.

| Object | Created here? | Notes |
|---|---|---|
| `lists`, `todos` | Yes, demo `setup.sql` | UUIDs, timestamptz, publication `powersync` |
| `task`, `store`, `app_user`, `store_user_link` | No | Named only in sync SQL |
| Mongo database `powersync_datafy` | By use of the URI | Volume `mongo_storage` |
| Mongo database `powersync_demo` | Commented URI in `v2/` | Not the active v2 storage |
| Postgres volume `pg_data` | Compose volume | Demo source data and, separately, the v2 bucket database. They are different compose projects, so the volume name can exist twice. **Inferred** from Compose project scoping |

`setup.sql` begins with `\connect postgres;`.

## Deployment and environments

No Dockerfile for the PowerSync service itself (the image is pulled). No Kubernetes, Terraform, systemd unit, or Bitbucket pipeline.

Paths that point at a specific machine:

| Path | File |
|---|---|
| `/home/ubuntu/datafy_powersync/v2/powersync.yaml` | `v2/docker-compose.yaml` |
| `/home/ubuntu/datafy_powersync/v2/config` | `v2/docker-compose.yaml` |
| `/home/ubuntu/powersync/powersync.yaml` and `/home/ubuntu/powersync/config` | `old.txt` |

Whether those directories exist on a server is **Unknown / Requires Confirmation**.

External Docker network `supabase_network_powersync_datafy` is `external: true` in `Mackup.yml`. The comment says the name comes from `supabase_network_${project_id}` and that `project_id` is in `config.toml`. There is no `config.toml` in this repo. The same network is commented out in `demos/supabase/docker-compose.yaml`.

Regions: the RDS hostname contains `eu-west-1`. No other region is set.

## Security

| Topic | What git shows |
|---|---|
| RDS credential | Four files under `v2/` contain a `supabase_admin` password in a URI. Silicon Ark does not repeat the password. Treat it as exposed and rotate it. History will keep the old value until the repository history is rewritten, which this documentation does not do |
| SSL | `sslmode: disable` on local URIs and on the RDS URI. `old.txt` notes `ssl: true` and `rejectUnauthorized: false` in its alternate YAML. That file is not the active config |
| Telemetry | Sharing enabled in `config/powersync.yaml` (`disable_telemetry_sharing: false`) |
| Published ports | PowerSync `${PS_PORT}` or `8080`, Mongo `27017`, v2 Postgres `5431`, demo client `${DEMO_CLIENT_PORT}` or `4170`, demo backend `${DEMO_BACKEND_PORT}`. No `127.0.0.1` bind |
| Local JWKS | Block left commented. Comment says to enable it in production |
| Demo keys | `POWERSYNC_PUBLIC_KEY` and `POWERSYNC_PRIVATE_KEY` are env references. Comment in `ps-nodejs-demo.yaml`: "Keys here for demonstration" |
| Ignore rules | `.gitignore` does not list `.env`. `example.env` and the `v2/` YAML are tracked |
| Image pin | `latest` for PowerSync and Postgres |

No application-level rate limit, RLS policy, or audit log is implemented in this repository.

## Development and testing

There is no test script, no CI file, and no `package.json` at the repo root. The demo app scripts are `dev`, `build` (`tsc -b && vite build`), `preview`, and `start` (`pnpm build && pnpm preview`).

How to start, from the files rather than from a root README:

| Stack | Command the files imply | Gap |
|---|---|---|
| Root | `docker compose up` with `example.env` present as `.env` | Missing `./powersync.yaml`, broken `PS_PG_URI` host, `network_mode: bridge`, config path commented out |
| v2 | `docker compose up` inside `v2/` | Absolute `/home/ubuntu/...` mounts, RDS URI, storage host/port disagreement, config path commented out |
| Node demo | `docker compose up` in `demos/nodejs`, as that README says | `DEMO_*` env vars are not in `example.env`. Sync rules and `PS_DATA_SOURCE_URI` / `PS_PG_URI` disagree. README port 3030 |
| Supabase demo | README: `supabase start`, then `docker compose up` in `demos/supabase` | Supabase CLI project is not in git. Compose file does not start the UI. `Mackup.yml` does, from the wrong directory for its dockerfile |

`demos/nodejs/README.md` says to delete the container and volumes if `setup.sql` must run again.

Schema references for the editor (`schema/schema.json`) are not in the tree.

## Monitoring

| Mechanism | Where |
|---|---|
| Liveness `/probes/liveness` | `services/powersync.yaml` only |
| `/health` curl | `old.txt` only |
| `pg_isready` | `services/postgres.yaml`, `v2/docker-compose.yaml` |
| Mongo ping | `services/mongo.yaml` only |
| PowerSync telemetry | Enabled in `config/powersync.yaml`. `client_endpoints: []` |
| Logs | No log driver, no shipper |

## Related Silicon Ark sets

These are other documents. They are not imports of this repo.

| Set | Why it comes up | Conflict with this repo |
|---|---|---|
| `flutter_app` | PowerSync client, `sync_rules.yaml`, tables `task`, `store`, `app_user`, `store_user_link` | Different buckets and a JourneyApps cloud URL. `active = 1` is documented there and absent here |
| `flutterflow_sync` | A `sync_rules.yaml` with the same YAML shape | That set says the YAML is not what the server executes. Different bucket list |
| `datafy-supabase-functions` | Writes `store_user_link` metrics | No reference to PowerSync or to this repo |
| `flutter_webportal`, `datafy_web` | Same table names | No reference to this repo |

Whether `datafy_powersync` was an experiment toward those products or a live self-hosted service is **Unknown / Requires Confirmation**.

## Source files that are not documentation

`README.md` is empty. `old.txt` is a scratch paste. `Mackup.yml` is Compose. Demo READMEs describe upstream todos and disagree with the sync SQL checked in beside them. Prefer the compose file and the YAML it mounts over the demo README when they disagree, and record the disagreement rather than picking a silent winner. The disagreements are in `prd.md` FR-011 and FR-012 and in `business-rules.md` BR-040 through BR-045.
