Skip to content

Commit 9999c3a

Browse files
committed
Update files
1 parent 28aa539 commit 9999c3a

8 files changed

Lines changed: 109 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added — Cloud control plane: Postgres driver support ⭐
11+
- **`buildControlDriver` in `@objectstack/service-cloud/cloud-stack.ts`** now recognises `postgres://`, `postgresql://`, and `pg://` URLs and dispatches them to `@objectstack/driver-sql` with `client: 'pg'`. Previously only `libsql:` / `https:` (Turso) and `file:` (SQLite) were recognised, so a `postgres://…` value was silently rewritten into a SQLite filename — a particularly nasty data-routing bug in production. The function now eagerly `import('pg')` so a missing-driver situation surfaces with an explicit, actionable error at boot.
12+
- **Pool sizing knobs**: `OS_CONTROL_PG_POOL_MIN` (default `0`) and `OS_CONTROL_PG_POOL_MAX` (default `10`) tune the knex `pg` pool without code changes.
13+
- **`pg` promoted to a runtime dependency of `apps/cloud`** so `pnpm deploy --prod` ships it inside the Docker image; image size unchanged at ~691 MB.
14+
- **`.env.cloudflare.secrets.example`, `setup-cloudflare-secrets.sh` (cloud + objectos), and `apps/cloud/README.md`** updated with the new env vars and a "Postgres in production" section that covers Neon / Supabase / RDS / Hyperdrive guidance.
15+
- **End-to-end verified**: `docker run` with `OS_CONTROL_DATABASE_URL=postgres://os:test@pg-test:5432/cloud` boots cleanly, `/api/v1/health` returns 200, and the full `sys_*` control-plane schema (Accounts, Activity, Agent, API Keys, Apps, Audit, Flows, OAuth, Organization, Packages, …) is materialised in Postgres on first boot.
16+
1017
### Changed — Slimmer production Docker images (`apps/objectos`, `apps/cloud`)
1118
- **3-stage builder → pruner → runner Dockerfiles**: full pnpm workspace is restored only in the builder; the pruner runs `pnpm --filter ... deploy --prod --legacy /deploy` to materialize a flat, devDeps-stripped tree; the runner copies just `/deploy` plus the freshly built `dist/` of the target app. CMD now runs `node node_modules/@objectstack/cli/bin/run.js serve dist/objectstack.config.js --prebuilt` directly — no `pnpm` at runtime.
1219
- **`@objectstack/cli` promoted to `dependencies`** in both `apps/objectos/package.json` and `apps/cloud/package.json` so the production entrypoint survives `--prod` pruning.

apps/cloud/.env.cloudflare.secrets.example

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,25 @@
33
# this file across both apps.
44

55
# ── Required for both apps ─────────────────────────────────────────────────
6-
# Remote libSQL/Turso URL — Cloudflare Containers' filesystem is ephemeral,
7-
# do NOT use file:/data/...
6+
# Control-plane database URL. Cloudflare Containers' local filesystem is
7+
# **ephemeral**, so a remote database is mandatory in production.
8+
#
9+
# Supported schemes:
10+
# • postgres://user:pass@host:5432/db (recommended for apps/cloud)
11+
# • postgresql://user:pass@host/db
12+
# • libsql://<db>.turso.io (Turso / libSQL)
13+
# • https://<db>.turso.io (Turso HTTP)
14+
# • file:/data/control.db (local Docker only — data lost on cold start)
15+
#
16+
# For apps/cloud, you almost certainly want Postgres in production.
17+
# Tune connection pool size with OS_CONTROL_PG_POOL_MIN / OS_CONTROL_PG_POOL_MAX
18+
# (defaults: 0 / 10).
19+
#
20+
# `OS_CONTROL_DATABASE_URL` takes priority over `OS_DATABASE_URL` when both are set.
821
OS_DATABASE_URL=
22+
# Optional: dedicated URL for the *control plane* if it differs from OS_DATABASE_URL.
23+
# OS_CONTROL_DATABASE_URL=postgres://user:pass@host:5432/objectstack_cloud
24+
# Auth token for libSQL/Turso. Not used by Postgres (use the URL's password instead).
925
OS_DATABASE_AUTH_TOKEN=
1026
# Cookie/session signing secret. Generate with: openssl rand -hex 32
1127
AUTH_SECRET=

apps/cloud/README.md

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,14 @@ docker buildx build --platform linux/amd64 \
9393
wrangler containers push \
9494
registry.cloudflare.com/<account-id>/objectstack-cloud:latest
9595

96-
# Secrets — the control plane MUST be on remote libSQL/Turso; the
97-
# container filesystem is wiped on cold-start.
96+
# Secrets — the control plane MUST be on a remote database. The container
97+
# filesystem is wiped on cold-start, so file:/... will lose all data.
98+
# For production, Postgres is recommended:
99+
# postgres://user:pass@host:5432/db
100+
# libSQL/Turso also works:
101+
# libsql://<db>.turso.io + OS_DATABASE_AUTH_TOKEN
98102
wrangler secret put OS_DATABASE_URL --config apps/cloud/wrangler.toml
99-
wrangler secret put OS_DATABASE_AUTH_TOKEN --config apps/cloud/wrangler.toml
103+
wrangler secret put OS_DATABASE_AUTH_TOKEN --config apps/cloud/wrangler.toml # libSQL only
100104
wrangler secret put AUTH_SECRET --config apps/cloud/wrangler.toml
101105
wrangler secret put TURSO_API_TOKEN --config apps/cloud/wrangler.toml
102106
wrangler secret put TURSO_ORG_NAME --config apps/cloud/wrangler.toml
@@ -109,11 +113,33 @@ Required runtime env vars (set as Cloudflare secrets, **not** in
109113

110114
| Var | Purpose |
111115
|---|---|
112-
| `OS_DATABASE_URL` | `libsql://<db>.turso.io` — control DB. |
113-
| `OS_DATABASE_AUTH_TOKEN` | Turso auth token. |
116+
| `OS_DATABASE_URL` | Control-plane DB URL. Supports `postgres://…`, `postgresql://…`, `libsql://…`, `https://…` (Turso), or `file:/…` (local Docker only). |
117+
| `OS_CONTROL_DATABASE_URL` | Optional override for the control DB when it differs from `OS_DATABASE_URL`. Takes priority when both are set. |
118+
| `OS_DATABASE_AUTH_TOKEN` | Auth token for libSQL/Turso. Unused for Postgres (put the password in the URL). |
119+
| `OS_CONTROL_PG_POOL_MIN` / `OS_CONTROL_PG_POOL_MAX` | Postgres pool sizing (default `0` / `10`). |
114120
| `AUTH_SECRET` | Cookie/session signing secret. |
115121
| `TURSO_API_TOKEN` / `TURSO_ORG_NAME` | Used by the provisioning workflow to create per-project Turso DBs. |
116122

123+
### Postgres in production
124+
125+
`apps/cloud` ships with the `pg` driver included. To run the control plane
126+
on Postgres (Neon / Supabase / RDS / Cloudflare Hyperdrive / self-hosted):
127+
128+
```bash
129+
export OS_DATABASE_URL='postgres://user:pass@host:5432/objectstack_cloud'
130+
# Optional fine-tuning:
131+
export OS_CONTROL_PG_POOL_MAX=20
132+
```
133+
134+
Schema migrations are applied automatically on first boot by the
135+
`@objectstack/driver-sql` engine using knex. The cloud control plane
136+
expects a database it can DDL freely — point it at a dedicated database
137+
(not one shared with unrelated tables).
138+
139+
> **Hyperdrive note**: Cloudflare Hyperdrive accelerates Postgres
140+
> connections from Workers, not Containers. For Containers, point
141+
> `OS_DATABASE_URL` directly at your Postgres instance.
142+
117143
Pair this with an `apps/objectos` deployment (see its README) and set
118144
`OS_CLOUD_URL=https://<cloud-worker>.<account>.workers.dev` on the
119145
runtime node so it talks to this control plane.

apps/cloud/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"dependencies": {
2424
"@hono/node-server": "^2.0.2",
2525
"@libsql/client": "^0.17.3",
26+
"@objectstack/cli": "workspace:*",
2627
"@objectstack/driver-memory": "workspace:*",
2728
"@objectstack/driver-sql": "workspace:*",
2829
"@objectstack/driver-turso": "workspace:*",
@@ -42,17 +43,18 @@
4243
"@objectstack/service-package": "workspace:*",
4344
"@objectstack/service-tenant": "workspace:*",
4445
"@objectstack/spec": "workspace:*",
45-
"@objectstack/cli": "workspace:*",
46-
"hono": "^4.12.18"
46+
"hono": "^4.12.18",
47+
"pg": "^8.20.0"
4748
},
4849
"devDependencies": {
4950
"@cloudflare/containers": "^0.3.3",
5051
"@cloudflare/workers-types": "^4.20260510.1",
52+
"@types/pg": "^8.20.0",
5153
"esbuild": "^0.28.0",
5254
"ts-node": "^10.9.2",
5355
"tsup": "^8.5.1",
5456
"tsx": "^4.21.0",
5557
"typescript": "^6.0.3",
5658
"wrangler": "^4.90.0"
5759
}
58-
}
60+
}

apps/cloud/scripts/setup-cloudflare-secrets.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ case "$APP_BASENAME" in
3838
KEYS=( OS_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
3939
OS_CLOUD_URL OS_CLOUD_API_KEY OS_COOKIE_DOMAIN ) ;;
4040
cloud)
41-
KEYS=( OS_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
41+
KEYS=( OS_DATABASE_URL OS_CONTROL_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
42+
OS_CONTROL_PG_POOL_MIN OS_CONTROL_PG_POOL_MAX
4243
TURSO_API_TOKEN TURSO_ORG_NAME OS_COOKIE_DOMAIN ) ;;
4344
*)
4445
echo "✗ unknown app dir: $APP_BASENAME" >&2; exit 1 ;;

apps/objectos/scripts/setup-cloudflare-secrets.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ case "$APP_BASENAME" in
3838
KEYS=( OS_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
3939
OS_CLOUD_URL OS_CLOUD_API_KEY OS_COOKIE_DOMAIN ) ;;
4040
cloud)
41-
KEYS=( OS_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
41+
KEYS=( OS_DATABASE_URL OS_CONTROL_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
42+
OS_CONTROL_PG_POOL_MIN OS_CONTROL_PG_POOL_MAX
4243
TURSO_API_TOKEN TURSO_ORG_NAME OS_COOKIE_DOMAIN ) ;;
4344
*)
4445
echo "✗ unknown app dir: $APP_BASENAME" >&2; exit 1 ;;

packages/services/service-cloud/src/cloud-stack.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,37 @@ export interface CloudStackConfig {
5050

5151
async function buildControlDriver(url: string, authToken?: string): Promise<{
5252
driver: IDataDriver;
53-
driverName: 'sqlite' | 'turso';
53+
driverName: 'sqlite' | 'turso' | 'postgres';
5454
databaseUrl: string;
5555
}> {
56+
// Postgres / CockroachDB / any pg-wire database.
57+
// Accept both `postgres://` and `postgresql://` schemes; `pg://` is also recognised
58+
// for parity with the per-tenant artifact registry.
59+
if (/^(postgres(ql)?|pg):\/\//i.test(url)) {
60+
const { SqlDriver } = await import('@objectstack/driver-sql');
61+
// `pg` is a peer/optional dep of knex; bring it in here so a clear error
62+
// surfaces at boot if the host forgot to install it.
63+
try {
64+
await import('pg');
65+
} catch (err) {
66+
throw new Error(
67+
`[service-cloud] Control-plane URL "${url}" requires the "pg" driver. `
68+
+ `Add \`pg\` to your application dependencies. Original: ${(err as Error).message}`,
69+
);
70+
}
71+
const poolMin = Number.parseInt(process.env.OS_CONTROL_PG_POOL_MIN ?? '0', 10);
72+
const poolMax = Number.parseInt(process.env.OS_CONTROL_PG_POOL_MAX ?? '10', 10);
73+
const driver = new SqlDriver({
74+
client: 'pg',
75+
connection: url,
76+
pool: {
77+
min: Number.isFinite(poolMin) ? poolMin : 0,
78+
max: Number.isFinite(poolMax) ? poolMax : 10,
79+
},
80+
});
81+
return { driver: driver as unknown as IDataDriver, driverName: 'postgres', databaseUrl: url };
82+
}
83+
5684
if (/^(libsql|https?):\/\//i.test(url)) {
5785
const { TursoDriver } = await import('@objectstack/driver-turso');
5886
const driver = new TursoDriver({ url, authToken });

pnpm-lock.yaml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)