Summary
Evaluate (and optionally implement) swapping apps/host-cloudflare's db seam
from D1 (SQLite) to Neon (or PlanetScale) + Hyperdrive. The same
repository already runs the Postgres-over-Hyperdrive path in production
(apps/cloud), so the swap target is a proven in-repo pattern rather than new
infrastructure.
Background
apps/host-cloudflare is the single-tenant self-host-on-Cloudflare template.
Its db seam is D1 (SQLite) via the shared FumaDB assembly (src/db/d1.ts,
README.md). FumaDB already supports postgresql/mysql providers
(packages/core/fumadb/src/shared/providers.ts), and apps/cloud already wires
postgres.js + Hyperdrive + a pg Drizzle schema (apps/cloud/wrangler.jsonc,
apps/cloud/src/db/db.ts).
Motivation: the D1 workarounds in src/db/d1.ts are the cost
Three documented D1 limitations are worked around today, all of which disappear
with Postgres/MySQL over Hyperdrive:
interactiveTransactions: false (d1.ts:54) - D1 rejects interactive
transactions, so multi-statement runtime writes (e.g. adding a source that
derives many tools) auto-commit per statement and lose atomicity. This is a
correctness issue, not just convenience. Postgres/MySQL restore real
transactions.
maxBoundParameters: 100 (d1.ts:61) - D1 caps bound parameters at 100,
forcing createMany micro-batching for wide tables like tool. Postgres
(up to 65535 params) removes the cap and the batching logic.
~1-2MB value cap -> R2 blob offload (d1.ts:71,
data-migrations.ts copyGoogleOpenApiSpecBlobsToR2) - large OpenAPI specs /
introspection snapshots are pushed to R2 to dodge D1's value ceiling.
Postgres TOAST handles large values natively (R2 seam can stay for truly huge
blobs / cost reasons, but the hard ceiling goes away).
Plus D1's 10GB-per-database cap and regional constraints are lifted by
Neon/PlanetScale.
Candidates (code effort is identical)
Both Neon and PlanetScale Postgres target FumaDB's postgresql provider and
reuse the exact postgres.js + pg Drizzle path apps/cloud already has
(apps/cloud/src/db/db.ts). Porting resolveConnectionString / makeSql and
switching provider: "sqlite" -> "postgresql" in d1.ts is the same low-
friction route for either. (An earlier note treated PlanetScale as MySQL/Vitess;
that objection does not apply to PlanetScale's Postgres product.) Both are
reached from the Worker via Hyperdrive. So the choice is operational/cost, not
code:
- Neon - serverless, scale-to-zero, free tier, no minimum spend. Best fit
for a low/spiky single-tenant template; idle cost trends to $0. Cold-start on
resume (Hyperdrive caching partly mitigates).
- PlanetScale Postgres - provisioned, always-on compute, no scale-to-zero.
Predictable low latency / high sustained throughput, but pays even when idle.
That profile is really apps/cloud's territory, not this template's.
Pricing comparison (early 2026, verify before deciding)
| Option |
Free tier |
Paid from |
scale-to-zero |
Notes |
| D1 (current) |
5GB / 150M reads / 3M writes per mo |
included in Workers Paid |
yes |
no external account/secret; incremental cost ~$0 |
| Cloudflare Hyperdrive |
included free on Workers Paid ($5/mo) - pooling + caching |
$0 extra |
n/a |
required for external Postgres, but itself unmetered |
| Neon |
100 CU-hours/mo, 0.5GB/project, 5GB egress |
Launch $0.106/CU-hour + $0.35/GB-mo storage, no minimum |
yes |
idle compute scales to zero |
| PlanetScale Postgres |
minimal/limited |
PS-5 $5/mo (512MB, 10GB incl.) / PS-10 $10/mo / HA $15-30 / Metal $50+ |
no |
always-on; storage overage $0.125/GB, egress 100GB then $0.06/GB |
Cost ordering for this app's profile: D1 (~free, already on Workers) ≈ Neon
free tier < PlanetScale ($5+/mo always-on). On price alone, keeping D1 is the
floor; if an external Postgres is required, Neon is cheaper than PlanetScale for
low/spiky single-tenant traffic. Hyperdrive adds no cost beyond the Workers Paid
plan the deploy likely already has.
Recommendation
Neon + Hyperdrive if swapping. Same code effort as PlanetScale Postgres but
cheaper for this template's low/spiky, single-tenant profile (scale-to-zero +
free tier vs. always-on $5+/mo). Choose PlanetScale Postgres only if predictable
low latency / high sustained throughput becomes a hard requirement.
Trade-off to weigh
host-cloudflare's reason to exist is a zero-external-dependency single
Worker self-host template (README.md: single-tenant, members/keys in
Cloudflare Access). D1 auto-provisions on wrangler deploy with no external
account or connection-string secret and is co-located with the Worker.
Neon/PlanetScale add an external account + secret + Hyperdrive setup. The swap
is worth it if D1 limits are being hit or transactional correctness matters;
otherwise D1 remains the right default for the template (large multi-tenant
scale is already apps/cloud's job).
Tasks
Additional Context
Originated from a code investigation of apps/host-cloudflare's storage layer.
Key references: apps/host-cloudflare/src/db/d1.ts,
apps/cloud/src/db/db.ts, apps/cloud/wrangler.jsonc,
packages/core/fumadb/src/shared/providers.ts.
Summary
Evaluate (and optionally implement) swapping
apps/host-cloudflare's db seamfrom D1 (SQLite) to Neon (or PlanetScale) + Hyperdrive. The same
repository already runs the Postgres-over-Hyperdrive path in production
(
apps/cloud), so the swap target is a proven in-repo pattern rather than newinfrastructure.
Background
apps/host-cloudflareis the single-tenant self-host-on-Cloudflare template.Its db seam is D1 (SQLite) via the shared FumaDB assembly (
src/db/d1.ts,README.md). FumaDB already supportspostgresql/mysqlproviders(
packages/core/fumadb/src/shared/providers.ts), andapps/cloudalready wirespostgres.js+ Hyperdrive + a pg Drizzle schema (apps/cloud/wrangler.jsonc,apps/cloud/src/db/db.ts).Motivation: the D1 workarounds in
src/db/d1.tsare the costThree documented D1 limitations are worked around today, all of which disappear
with Postgres/MySQL over Hyperdrive:
interactiveTransactions: false(d1.ts:54) - D1 rejects interactivetransactions, so multi-statement runtime writes (e.g. adding a source that
derives many tools) auto-commit per statement and lose atomicity. This is a
correctness issue, not just convenience. Postgres/MySQL restore real
transactions.
maxBoundParameters: 100(d1.ts:61) - D1 caps bound parameters at 100,forcing
createManymicro-batching for wide tables liketool. Postgres(up to 65535 params) removes the cap and the batching logic.
~1-2MB value cap-> R2 blob offload (d1.ts:71,data-migrations.tscopyGoogleOpenApiSpecBlobsToR2) - large OpenAPI specs /introspection snapshots are pushed to R2 to dodge D1's value ceiling.
Postgres TOAST handles large values natively (R2 seam can stay for truly huge
blobs / cost reasons, but the hard ceiling goes away).
Plus D1's 10GB-per-database cap and regional constraints are lifted by
Neon/PlanetScale.
Candidates (code effort is identical)
Both Neon and PlanetScale Postgres target FumaDB's
postgresqlprovider andreuse the exact
postgres.js+ pg Drizzle pathapps/cloudalready has(
apps/cloud/src/db/db.ts). PortingresolveConnectionString/makeSqlandswitching
provider: "sqlite"->"postgresql"ind1.tsis the same low-friction route for either. (An earlier note treated PlanetScale as MySQL/Vitess;
that objection does not apply to PlanetScale's Postgres product.) Both are
reached from the Worker via Hyperdrive. So the choice is operational/cost, not
code:
for a low/spiky single-tenant template; idle cost trends to $0. Cold-start on
resume (Hyperdrive caching partly mitigates).
Predictable low latency / high sustained throughput, but pays even when idle.
That profile is really
apps/cloud's territory, not this template's.Pricing comparison (early 2026, verify before deciding)
Cost ordering for this app's profile: D1 (~free, already on Workers) ≈ Neon
free tier < PlanetScale ($5+/mo always-on). On price alone, keeping D1 is the
floor; if an external Postgres is required, Neon is cheaper than PlanetScale for
low/spiky single-tenant traffic. Hyperdrive adds no cost beyond the Workers Paid
plan the deploy likely already has.
Recommendation
Neon + Hyperdrive if swapping. Same code effort as PlanetScale Postgres but
cheaper for this template's low/spiky, single-tenant profile (scale-to-zero +
free tier vs. always-on $5+/mo). Choose PlanetScale Postgres only if predictable
low latency / high sustained throughput becomes a hard requirement.
Trade-off to weigh
host-cloudflare's reason to exist is a zero-external-dependency singleWorker self-host template (
README.md: single-tenant, members/keys inCloudflare Access). D1 auto-provisions on
wrangler deploywith no externalaccount or connection-string secret and is co-located with the Worker.
Neon/PlanetScale add an external account + secret + Hyperdrive setup. The swap
is worth it if D1 limits are being hit or transactional correctness matters;
otherwise D1 remains the right default for the template (large multi-tenant
scale is already
apps/cloud's job).Tasks
neon.ts/hyperdrive.tsDbProvider handle modeledon
apps/cloud/src/db/db.ts(postgres.js + Hyperdrive)sqlite->postgresql; drop the three D1workarounds in
d1.tshyperdrivebinding towrangler.jsonc; document connection-stringsecret + setup in
README.md/RUNNING.mdensurevs. out-of-band like cloud)Additional Context
Originated from a code investigation of
apps/host-cloudflare's storage layer.Key references:
apps/host-cloudflare/src/db/d1.ts,apps/cloud/src/db/db.ts,apps/cloud/wrangler.jsonc,packages/core/fumadb/src/shared/providers.ts.