Skip to content
Merged
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
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ jobs:
env:
WORKERD_COMPAT_DATE: ${{ matrix.compat_date }}
WORKERD_COMPAT_FLAGS: ${{ matrix.flags }}
WORKERD_KNOWN_FAILURES: ${{ matrix.known_failures }}
WORKERD_CLOSE_REJECTIONS: ${{ matrix.close_rejections }}
WORKERD_TLS_PORT: 55433
WORKERD_TLS_CA: ${{ runner.temp }}/server.crt
Expand Down
9 changes: 2 additions & 7 deletions .github/workflows/workerd-edge-cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,15 @@ concurrency:

jobs:
cleanup:
name: Delete the edge Previews (${{ matrix.compat_date }})
name: Delete the edge Preview
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
compat_date: ['2025-01-01', '2026-10-12']
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- name: Delete the Preview
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
COMPAT_DATE: ${{ matrix.compat_date }}
run: npx --yes wrangler@4.151.1 preview delete --worker-name postgresjs-edge --name "pr-$PR_NUMBER-${COMPAT_DATE//-/}" --skip-confirmation
run: npx --yes wrangler@4.151.1 preview delete --worker-name postgresjs-edge --name "pr-$PR_NUMBER" --skip-confirmation
17 changes: 3 additions & 14 deletions .github/workflows/workerd-edge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ on:
pull_request:
paths-ignore: ['**/*.md', 'codemod/**', '.github/workflows/codemod.yml']
workflow_dispatch:
inputs:
hyperdrive_rounds:
description: Hyperdrive rounds per build
default: '1'
push:
branches: [main]
tags: ['v*']
Expand All @@ -22,36 +18,29 @@ concurrency:

jobs:
edge:
name: Cloudflare edge Preview (${{ matrix.compat_date }}) on Neon and Supabase
name: Cloudflare edge Preview on Neon and Supabase
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
compat_date: ['2025-01-01', '2026-10-12']
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
EDGE_HYPERDRIVE_ROUNDS: ${{ inputs.hyperdrive_rounds || '1' }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '24'
- run: npx --yes wrangler@4.151.1 whoami
- run: npm run build:cf
- run: node tests-startup/workerd-edge.mjs prepare "$RUNNER_TEMP/edge" "${{ matrix.compat_date }}"
- run: node tests-startup/workerd-edge.mjs prepare "$RUNNER_TEMP/edge" 2026-10-12
- name: Deploy the Preview and run the scenarios
env:
NEON_DATABASE_URL: ${{ secrets.NEON_DATABASE_URL }}
NEON_POOLED_URL: ${{ secrets.NEON_POOLED_URL }}
SUPABASE_DATABASE_URL: ${{ secrets.SUPABASE_DATABASE_URL }}
PR_NUMBER: ${{ github.event.pull_request.number }}
COMPAT_DATE: ${{ matrix.compat_date }}
run: |
base=$([ -n "$PR_NUMBER" ] && echo "pr-$PR_NUMBER" || echo "$GITHUB_REF_NAME" | tr 'A-Z._/' 'a-z---')
name="$base-${COMPAT_DATE//-/}"
name=$([ -n "$PR_NUMBER" ] && echo "pr-$PR_NUMBER" || echo "$GITHUB_REF_NAME" | tr 'A-Z._/' 'a-z---')
EDGE_TOKEN=$(openssl rand -hex 24)
echo "::add-mask::$EDGE_TOKEN"
export EDGE_TOKEN
Expand Down
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,7 @@ On Cloudflare Workers the package connects through Cloudflare's [`cloudflare:soc
compatibility_flags = ["nodejs_compat"]
```

CI runs the Workers scenarios at compatibility dates 2025-01-01 (with and without `unhandled_rejection_after_microtask_checkpoint`), 2025-09-15 and 2026-10-12 in local workerd, and on Cloudflare's network against Neon, directly, through its pooler and through Hyperdrive, and against Supabase through Hyperdrive.
CI runs the Workers scenarios at compatibility dates 2025-01-01 (with and without `unhandled_rejection_after_microtask_checkpoint`), 2025-09-15 and 2026-10-12 in local workerd. On Cloudflare's network, at 2026-10-12, it runs the ones that depend on the network: verified TLS, a private certificate authority, transactions through poolers, Hyperdrive and a client left open, against Neon directly, through its pooler and through Hyperdrive, and against Supabase through its pooler and through Hyperdrive.

On compatibility dates before 2026-03-03, workerd can report a rejection as unhandled before the code awaiting it has handled it ([cloudflare/workerd#6020](https://github.com/cloudflare/workerd/issues/6020)). Closing a socket rejects the read the driver always has pending, so each closed connection logs `Stream was cancelled.` or `This socket has been closed.` as an unhandled rejection on those dates, whether `sql.end()` closed it, a timeout did, or the request finished with the client still open. A connection the network or a pooler such as Hyperdrive drops logs the socket error instead, such as `Network connection lost.`, with the stack of the query it failed. The driver handles it, and queries and their errors are unaffected. `sql.end()` sends Terminate and closes at once rather than waiting a round trip for the server to close first, so the log line is the cost of a fast `end()`. To remove it, move to 2026-03-03 or later, or add the flag:

Expand Down
4 changes: 2 additions & 2 deletions tests-startup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ Graceful `end()` runs every query accepted before the call, including plain quer

The portable integration suite checks all 22 built-in element/array OID pairs against PostgreSQL, roundtrips built-in arrays with fetching on/off, preserves independent custom parser/serializer overrides, and covers transforms, SQL NULL positions, quoted NULL text, and user-defined arrays. Numeric NULL assertions use strict null checks so JSON serialization cannot hide NaN. Before this change, discovery returned text NULL as a string, integer NULL as NaN, and JSON NULL threw; the shared parser now recognizes unquoted NULL before applying scalar parsers.

`npm run test:workerd -- . <isolated-postgres-port>` runs the driver in local workerd through `createTestHarness()` from pinned Wrangler 4.151.1, which it installs without saving (requires Node 22 or newer). It imports `@steadybrew/postgresjs` through the package's export map, which the `workerd` condition resolves to the generated `cf/` library over `cloudflare:sockets`. `WORKERD_COMPAT_DATE` sets the worker's `compatibility_date` (default `2025-01-01`); CI runs 2025-01-01, 2025-09-15 and the newest date the bundled runtime accepts. It covers cold reservation, built-in arrays/NULL values with fetching on/off, handled catalog failure, and the Cloudflare timer and socket-close fixes: `timers` (idle_timeout and max_lifetime firing, then a query on a recycled connection), `connect-timeout` (a server that accepts and never answers), `end-timeout` (`sql.end({ timeout: 0.2 })` over an in-flight `pg_sleep`), `end-plain` (several clients each ending within a bound) and `end-while-connecting` (`sql.end({ timeout: 0 })` before the socket opens). `copy-out` reads a `COPY TO` stream with a slow consumer, so the socket must pause and resume ([#975](https://github.com/porsager/postgres/issues/975)); `ssl-prefer-declined` connects with `ssl: 'prefer'` to a server without SSL; `subscribe` delivers one logical replication insert (the cluster needs `wal_level = logical` and a replication `pg_hba` entry); `diagnostics-channel` checks that `node:diagnostics_channel` publishes and traces in workerd. `end-tls` and `tls-untrusted-ca` run only when `WORKERD_TLS_PORT` points at an SSL-enabled PostgreSQL and `WORKERD_TLS_CA` at its certificate (which needs a `subjectAltName` for `127.0.0.1`): `end-tls` runs in a harness whose workerd gets the certificate as `SSL_CERT_FILE`, and `tls-untrusted-ca` in one that does not, so it must fail with the certificate error rather than `CONNECT_TIMEOUT` ([#1213](https://github.com/porsager/postgres/issues/1213)). `WORKERD_KNOWN_FAILURES` lists scenarios expected to fail on the build under test; the run fails if one of them passes, as it does if any other scenario fails. Each scenario starts from `reset()` (fresh storage and logs), runs one bounded request, and fails on any unhandled rejection or uncaught error in the captured runtime logs; the worker also returns the unhandled rejections it saw in its response, and the runner reports each scenario's close-time rejections as `closeRejections`. A close-time rejection ("Stream was cancelled", from workerd 1.20261008.1 "This socket has been closed", or "Network connection lost" when the connection is dropped rather than closed) is logged on every close on compatibility dates before 2026-03-03 without the `unhandled_rejection_after_microtask_checkpoint` flag; `WORKERD_CLOSE_REJECTIONS=logged` marks those matrix entries, and elsewhere any close-time rejection fails the run. The matrix also runs 2025-01-01 with that flag. `no-end` runs a query and returns without ending the client, then polls `pg_stat_activity` by `application_name` until the backend is gone, failing after 10 s. The worker replaces `setTimeout` with a version that throws on extra arguments, as the Cloudflare runtime does ([#1088](https://github.com/porsager/postgres/issues/1088)). The runner retains logs under its printed temporary artifact directory.
`npm run test:workerd -- . <isolated-postgres-port>` runs the driver in local workerd through `createTestHarness()` from pinned Wrangler 4.151.1, which it installs without saving (requires Node 22 or newer). It imports `@steadybrew/postgresjs` through the package's export map, which the `workerd` condition resolves to the generated `cf/` library over `cloudflare:sockets`. `WORKERD_COMPAT_DATE` sets the worker's `compatibility_date` (default `2025-01-01`); CI runs 2025-01-01, 2025-09-15 and the newest date the bundled runtime accepts. It covers cold reservation, built-in arrays/NULL values with fetching on/off, handled catalog failure, and the Cloudflare timer and socket-close fixes: `timers` (idle_timeout and max_lifetime firing, then a query on a recycled connection), `connect-timeout` (a server that accepts and never answers), `end-timeout` (`sql.end({ timeout: 0.2 })` over an in-flight `pg_sleep`), `end-plain` (several clients each ending within a bound) and `end-while-connecting` (`sql.end({ timeout: 0 })` before the socket opens). `copy-out` reads a `COPY TO` stream with a slow consumer, so the socket must pause and resume ([#975](https://github.com/porsager/postgres/issues/975)); `ssl-prefer-declined` connects with `ssl: 'prefer'` to a server without SSL; `subscribe` delivers one logical replication insert (the cluster needs `wal_level = logical` and a replication `pg_hba` entry); `diagnostics-channel` checks that `node:diagnostics_channel` publishes and traces in workerd. `end-tls` and `tls-untrusted-ca` run only when `WORKERD_TLS_PORT` points at an SSL-enabled PostgreSQL and `WORKERD_TLS_CA` at its certificate (which needs a `subjectAltName` for `127.0.0.1`): `end-tls` runs in a harness whose workerd gets the certificate as `SSL_CERT_FILE`, and `tls-untrusted-ca` in one that does not, so it must fail with the certificate error rather than `CONNECT_TIMEOUT` ([#1213](https://github.com/porsager/postgres/issues/1213)). The run fails if any scenario fails. Each scenario starts from `reset()` (fresh storage and logs), runs one bounded request, and fails on any unhandled rejection or uncaught error in the captured runtime logs; the worker also returns the unhandled rejections it saw in its response, and the runner reports each scenario's close-time rejections as `closeRejections`. A close-time rejection ("Stream was cancelled", from workerd 1.20261008.1 "This socket has been closed", or "Network connection lost" when the connection is dropped rather than closed) is logged on every close on compatibility dates before 2026-03-03 without the `unhandled_rejection_after_microtask_checkpoint` flag; `WORKERD_CLOSE_REJECTIONS=logged` marks those matrix entries, and elsewhere any close-time rejection fails the run. The matrix also runs 2025-01-01 with that flag. `no-end` runs a query and returns without ending the client, then polls `pg_stat_activity` by `application_name` until the backend is gone, failing after 10 s. The worker replaces `setTimeout` with a version that throws on extra arguments, as the Cloudflare runtime does ([#1088](https://github.com/porsager/postgres/issues/1088)). The runner retains logs under its printed temporary artifact directory.

`.github/workflows/workerd-edge.yml` runs the same worker on Cloudflare's network as a Preview of the `postgresjs-edge` Worker, with `tests-startup/workerd-edge.mjs`. On pull requests, on main and on release tags it runs the scenarios against Neon, its pooler and Hyperdrive, and `workerd-edge-cleanup.yml` deletes a pull request's Previews when it closes. The CPU measurement runs only when dispatched by hand, so it does not appear among a commit's checks. `workerd-ab.yml` compares the `cf/` build of the dispatched branch with that of `ab_base`, which may be a release tag or an upstream tag such as `v3.4.9`: the CPU case turns off the worker's strict `setTimeout`, which upstream's timers fail. Each request connects, runs its queries and ends. The `supabase-false` and `supabase-true` targets run `select 1` unprepared against the Supabase CI project's pooler (plaintext) with `fetch_types` off and on, `neon-false` does the same with TLS against Neon, and `hyperdrive-true` connects through Hyperdrive with `prepare: true` and runs parameterized queries in parallel, as a production Worker does; `all` runs all four. Its `ab_copies` input bundles that many separate copies of each build, and each measured round runs on a copy no earlier round used, so every request measures the build's first use in a warm isolate; it takes a single target, and with 40 copies of each build the Worker is about 8 MB, 2 MB gzipped. Each request's `cpuTimeMs`, `wallTimeMs` and outcome come from Workers Logs, so the token needs Workers Metadata and Workers Content read access at account level. The job reports median, p95 and p99 per build and target, and a one-sided Mann-Whitney rank test each way. With `ab_gate` it is the release gate: it fails when the branch is at least 1 ms worse than `ab_base` at the median or p95 on any target and the rank test gives p < 0.01, or when it is killed for exceeding CPU in 2 percentage points more requests. `cpuTimeMs` is whole milliseconds, so a 1 ms difference alone can be rounding; the rank test uses every request. Failed requests are listed, and fail the job only when one variant loses more than 2% of its rounds.
`.github/workflows/workerd-edge.yml` runs the same worker on Cloudflare's network as a Preview of the `postgresjs-edge` Worker, with `tests-startup/workerd-edge.mjs`. On pull requests, on main and on release tags it runs, at compatibility date 2026-10-12, only the scenarios that depend on Cloudflare's network: `diagnostics-channel` in the production runtime, `ssl-verify` (verified TLS and SNI against Neon, which refuses plaintext), `tls-private-ca`, `pooled`, `no-end`, the `abort-*` transactions against Neon, its pooler, the Supabase pooler and Hyperdrive, and the three `hyperdrive*` cases; the others run only in local workerd, which covers them without a network. `workerd-edge-cleanup.yml` deletes a pull request's Preview when it closes. The CPU measurement runs only when dispatched by hand, so it does not appear among a commit's checks. `workerd-ab.yml` compares the `cf/` build of the dispatched branch with that of `ab_base`, which may be a release tag or an upstream tag such as `v3.4.9`: the CPU case turns off the worker's strict `setTimeout`, which upstream's timers fail. Each request connects, runs its queries and ends. The `supabase-false` and `supabase-true` targets run `select 1` unprepared against the Supabase CI project's pooler (plaintext) with `fetch_types` off and on, `neon-false` does the same with TLS against Neon, and `hyperdrive-true` connects through Hyperdrive with `prepare: true` and runs parameterized queries in parallel, as a production Worker does; `all` runs all four. Its `ab_copies` input bundles that many separate copies of each build, and each measured round runs on a copy no earlier round used, so every request measures the build's first use in a warm isolate; it takes a single target, and with 40 copies of each build the Worker is about 8 MB, 2 MB gzipped. Each request's `cpuTimeMs`, `wallTimeMs` and outcome come from Workers Logs, so the token needs Workers Metadata and Workers Content read access at account level. The job reports median, p95 and p99 per build and target, and a one-sided Mann-Whitney rank test each way. With `ab_gate` it is the release gate: it fails when the branch is at least 1 ms worse than `ab_base` at the median or p95 on any target and the rank test gives p < 0.01, or when it is killed for exceeding CPU in 2 percentage points more requests. `cpuTimeMs` is whole milliseconds, so a 1 ms difference alone can be rounding; the rank test uses every request. Failed requests are listed, and fail the job only when one variant loses more than 2% of its rounds.

The scope follows [#1219](https://github.com/porsager/postgres/issues/1219), [#751](https://github.com/porsager/postgres/issues/751), and [#1203](https://github.com/porsager/postgres/issues/1203) reservation ownership; [#1195](https://github.com/porsager/postgres/issues/1195) queued reconnect; [#789](https://github.com/porsager/postgres/issues/789) first-query type registration; [#1192](https://github.com/porsager/postgres/issues/1192) and [#1205](https://github.com/porsager/postgres/issues/1205) internal rejection handling; [#1223](https://github.com/porsager/postgres/issues/1223), [#1193](https://github.com/porsager/postgres/issues/1193), and [#1226](https://github.com/porsager/postgres/issues/1226) retry/state isolation; and [#1164](https://github.com/porsager/postgres/issues/1164) built-in arrays without discovery. Catalog traffic/caching from [#903](https://github.com/porsager/postgres/issues/903) is deferred.

Expand Down
Loading
Loading