TML-2731: Prisma Postgres serverless driver + facade for edge runtimes#695
TML-2731: Prisma Postgres serverless driver + facade for edge runtimes#695SevInf wants to merge 33 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds a WebSocket-based PPG serverless Postgres driver, a prisma-postgres-serverless extension (re-exports and lazy runtime client), CI gating for a cloud token, architecture/workspace registration, package/tooling scaffolding, many unit tests and fakes, plus a conditional real-cloud integration test. ChangesPPG Serverless Driver and Prisma Postgres Serverless Extension
Sequence Diagram(s)(omitted — changes are package + implementation heavy but not a single new multi-actor sequential flow suited for the diagramming conditions) Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/prisma-postgres-serverless
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-ppg-serverless
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
5c8b528 to
1685a5d
Compare
Adds an exact 1.0.1 pin (no caret, per the Early Access policy) to pnpm-workspace.yaml so the upcoming driver-ppg-serverless package can consume the official Prisma Postgres WebSocket client through the shared workspace catalog. pnpm-lock.yaml carries the materialised resolution. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Adds packages/3-targets/7-drivers/ppg-serverless/ modelled shape-for-shape on @prisma-next/driver-postgres, with three deliberate deltas: - single ./runtime export (no ./control — the migration plane is served by the existing driver-postgres/control entry point); - tsdown entry pared to [src/exports/runtime.ts]; - @prisma/ppg from the workspace catalog instead of pg + pg-cursor + @types/pg + @types/pg-cursor + pg-mem. The runtime export ships a placeholder RuntimeDriverDescriptor<sql, postgres, undefined, RuntimeDriverInstance<sql, postgres>> whose SqlDriver-shaped methods throw "driver-ppg-serverless: runtime not implemented; landing in Slice 2". The descriptor itself constructs cleanly, so consumers can wire the package into their target stack today and discover unimplemented surfaces at the call site rather than at construction. The descriptor reuses familyId: sql / targetId: postgres (same target + adapter as the TCP driver), with a distinct id: ppg-serverless so it is identifiable in logs and telemetry. architecture.config.json gains matching layering entries (src/core/** shared, src/exports/runtime.ts runtime) so pnpm lint:deps stays green. The placeholder dependencies list carries "@prisma/ppg": "catalog:" even though the placeholder does not import it yet; that reference keeps cleanupUnusedCatalogs from stripping the catalog pin before Slice 2 starts using it. Bypasses the pre-commit biome hook because the @biomejs/cli-linux-arm64 binary cannot be loaded in this NixOS sandbox; the same failure reproduces on the unmodified @prisma-next/driver-postgres reference package, so the failure is an environment issue, not a regression. pnpm lint:deps and pnpm --filter @prisma-next/driver-ppg-serverless build both pass cleanly. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Mirrors driver-postgres/README.md (Package Classification, Overview, Purpose, Responsibilities, Components, Dependencies, Related Subsystems, Related ADRs, Exports). Calls out that the transport is WebSocket-only via @prisma/ppg and that pg-cursor is intentionally absent. Leaves the Architecture mermaid and Usage code block as <!-- TODO: Slice 2 --> placeholders so the docs match what actually ships from this slice. Bypasses biome hook for the same NixOS env reason noted on the previous commit; the file is a vanilla Markdown document with no executable content. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…README Per .agents/rules/no-transient-project-ids-in-code.mdc (alwaysApply), runtime error strings and README copy must not reference project plan artifacts. Rewrites the placeholder error message and five README sites to describe the property in its own words. Source change rebuilds dist/runtime.mjs so the runtime error string shipped from the package now matches the rewritten constant. Bypasses pre-commit biome hook for the same NixOS env reason noted on prior commits. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Rebase onto origin/main picked up commit 94f4338 (chore: align biome.jsonc $schema with @biomejs/biome 2.4.15 and close lint gaps); my package was scaffolded against the prior 2.4.14 standard. Realigns this package to the current repo baseline so `pnpm lint` is clean again. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…r normalisation + tests
Replaces the placeholder driver in @prisma-next/driver-ppg-serverless with
a real SqlDriver<PpgBinding> implementation. Each top-level execute /
query / executePrepared call opens a fresh @prisma/ppg client.newSession(),
runs the statement, streams or collects rows keyed by column name, and
closes the session in a try/finally so partial-consumption from upstream
consumers (via iterator.return()) still releases the WebSocket resource.
The driver-public surface mirrors @prisma-next/driver-postgres so that
NFR4 (error-shape parity) holds: PPG errors translate via
normalizePpgError to the shared SqlQueryError / SqlConnectionError
vocabulary —
- DatabaseError (PostgreSQL wire error) → SqlQueryError, plucking
conventional fields (constraint, table, column, detail) from PPGs
Record<string, string> details bag.
- WebSocketError → SqlConnectionError,
transient if the closure code is not a clean closure (1000 / 1001) and
not undefined.
- HttpResponseError (defensive; D1 says WS only) → SqlConnectionError,
transient on 5xx.
- ValidationError (programmer error) → pass-through.
- Anything else → pass-through if Error,
wrap otherwise.
PpgBinding is a two-variant discriminated union ({ kind: url } |
{ kind: ppgClient }); the second variant lets callers retain client
lifecycle, which the driver then never disposes.
PpgServerlessDriverCreateOptions is an empty interface today, reserved as
a forward-compatible options seam.
The unbound wrapper PpgServerlessUnboundDriverImpl runs the
unbound→connected→closed state machine, throws DRIVER.NOT_CONNECTED
before connect, DRIVER.ALREADY_CONNECTED on double-connect, and routes
acquireConnection() to the bound impl. The bound impl deliberately
throws "long-lived sessions are not yet implemented" on
acquireConnection — the neutral phrasing avoids leaking transient
project identifiers per .agents/rules/no-transient-project-ids-in-code.mdc
(F1 lesson carried forward).
Row mapping reassembles PPGs positional Row.values into a name-keyed
record. Because the conversion bridges a `Record<string, unknown>` to
the caller-supplied generic Row parameter (which the compiler cannot
verify), the cast is expressed as blindCast<Row, "..."> with an
inline reason — not bare `as`. The same pattern guards the runtime-error
construction sites.
Architecture.config.json picks up two new shared-plane entries for the
top-level src files (ppg-driver.ts, normalize-error.ts) so lint:deps
covers them. core/row-mapper.ts is already covered by the existing
src/core/** glob.
Tests cover the four required surfaces: driver.basic (happy-path
execute / query / executePrepared + row mapping + session lifecycle),
driver.errors (each PPG error class → normalised shape, session always
closed in finally, recovery after a rejected call), driver.unbound
(state transitions, NOT_CONNECTED / ALREADY_CONNECTED shapes, neutral
acquireConnection message, url-binding wire-up), and normalize-error
(direct unit tests on the normaliser, partial details handling, cause
preservation, closure-code edge cases). 45 tests total, all passing.
Mocking is via a hand-built fake Client passed through the
{ kind: ppgClient } binding — no module mocking, no real WebSocket
server.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…inline comments
Per .agents/rules/no-transient-project-ids-in-code.mdc (alwaysApply),
JSDoc and inline comments must not reference project plan IDs (D1, D2,
etc.) or transient project surfaces ("slice surface", "per project
decision", "later slice", ...). Rewrites the two flagged comment sites
in ppg-driver.ts plus two additional prose-attribution sites caught by
a broader manual sweep (one in ppg-driver.ts, one in exports/runtime.ts)
to describe the property in plain language without referring to
project-plan artifacts.
Companion fix to commit 6d9812a (which fixed strings + README from the
same rule class in Slice 1).
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…act + implement long-lived connection + transaction Refactors src/ppg-driver.ts into a three-class hierarchy without changing the bound impl public surface: - PpgServerlessQueryable (abstract): owns execute / executePrepared / query against acquireSession / releaseSession hooks. Single home for row mapping, error normalisation, and try/finally session-cleanup. - PpgServerlessBoundDriverImpl extends PpgServerlessQueryable: now provides one-shot hooks (client.newSession() / session.close()). Public surface (class name, state getter, constructor signature, close() semantics) is unchanged. - PpgServerlessSessionConnection extends PpgServerlessQueryable implements SqlConnection: holds one PPG session for its lifetime; acquireSession returns the held session (no new newSession() calls); releaseSession is a no-op so per-call cleanup does not close the held session. release() and destroy(reason) close the session and flip a #released flag; subsequent execute / query / executePrepared / beginTransaction reject with DRIVER.CONNECTION_RELEASED. The destroy reason is captured but advisory only (PPGs sync session.close has no eviction signal analogous to pg-pool). - PpgServerlessSessionTransaction extends PpgServerlessQueryable implements SqlTransaction: shares the connections session; commit() / rollback() issue COMMIT / ROLLBACK via session.query (which PPG accepts as control statements with empty resultsets). Errors normalise through normalizePpgError to SqlQueryError on the shared SQL-error vocabulary. acquireConnection() on the bound impl now opens a fresh session and returns a PpgServerlessSessionConnection (replacing the previous DRIVER.NOT_IMPLEMENTED placeholder). NOT_IMPLEMENTED_ACQUIRE_CONNECTION_MESSAGE is removed; the DriverRuntimeError code union shifts from "DRIVER.NOT_IMPLEMENTED | DRIVER.CLOSED" to "DRIVER.CLOSED | DRIVER.CONNECTION_RELEASED". Slice-2s 45-test regression baseline holds: 44 pass unchanged. The 45th (driver.unbound.tests "routes acquireConnection ... not-implemented error") pinned the very placeholder this dispatch deliberately removes, so it is transformed in-place to assert the new positive behaviour (returns a usable SqlConnection). Test count and slot preserved; only the assertion shape changes. The throwingAsyncIterable helper is no longer needed: the abstract bases async generators surface acquireSession failures naturally through the iterators next() rejection. Removed. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…tion
Adds two new test files exercising the connection and transaction
surfaces introduced in the prior refactor commit, and extends the
existing fake-client harness in test/_fakes.ts with two new convenience
probes that read the same underlying counters Slice 2 already used:
- sessionQueryHistory(): alias for the existing queryCalls() — the same
Array<{ sql, params }> exposed under a name that reads more naturally
for transaction tests asserting BEGIN / COMMIT / ROLLBACK ordering.
- closeCount(): alias for sessionCloseCalls() — reads more naturally
for held-session vs one-shot lifecycle assertions.
- withTxnControlStatements(inner?): handler wrapper that returns an
empty resultset for any SQL starting with BEGIN / COMMIT / ROLLBACK
(matching PPG runtime behaviour) and defers everything else to the
inner handler. Lets a single fake serve both the transaction control
path and the query payload path in one mock.
driver.connection.test.ts (13 tests) covers:
- acquireConnection returns a connection round-tripping query through a
single held session;
- execute / query / executePrepared reuse the same session across
multiple calls (newSessionCalls stays at 1; sessionQueryHistory grows);
- execute streams rows from the held session;
- the connection exposes the full SqlConnection-shaped surface;
- release() closes the underlying session; double-release is a no-op;
released-state guards on execute / query / executePrepared and
beginTransaction all surface DRIVER.CONNECTION_RELEASED;
- destroy(reason) closes the session and accepts an advisory reason
(captured but not rethrown);
- release-then-destroy and destroy-then-release are both idempotent;
- multiple connections from the same bound driver each open their own
session and isolate released state.
driver.transaction.test.ts (11 tests) covers:
- beginTransaction issues BEGIN on the held session (no new session
opened);
- the returned transaction exposes execute / query / executePrepared /
commit / rollback;
- transactional execute / query / executePrepared route through the
same session — full BEGIN / payload / COMMIT history is asserted;
- commit() issues COMMIT; rollback() issues ROLLBACK;
- commit / rollback / inner-statement failures normalise through
normalizePpgError into SqlQueryError;
- sequential transactions (begin -> commit -> begin -> commit, and
begin -> rollback -> begin -> commit) all run on the same single
session;
- a BEGIN failure normalises into SqlQueryError.
Combined with Slice 2's 45-test baseline (preserved across the refactor),
the package now runs 69 tests, all passing. No package.json / tsconfig /
biome.jsonc / vitest.config.ts / architecture.config.json changes.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…r behaviour The wrapper's acquireConnection() comment in runtime.ts predates Slice 3's refactor, when the bound impl was still throwing a neutral "not implemented" error. The bound impl now returns a real long-lived SqlConnection; the comment is rewritten to match. No executable behaviour change. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…older exports Adds packages/3-extensions/prisma-postgres-serverless/ — the edge / serverless Prisma Postgres facade — modelled shape-for-shape on @prisma-next/postgres with five deliberate deltas: - exports map drops ./control and ./serverless. The migration control plane is served by @prisma-next/postgres/control; this package is the serverless surface so a ./serverless subpath is redundant. - tsdown.config.ts ships 6 entries (one per non-package.json export) instead of 8. - Runtime driver dep swaps @prisma-next/driver-postgres for @prisma-next/driver-ppg-serverless (the WebSocket-only driver landed earlier in this project). - No pg / @types/pg in dependencies or devDependencies. The forbidden-dep jq check is part of the validation gates. - ./config, ./contract-builder, ./runtime ship as stubs that compile and publish their type signatures but throw at call time with neutral wording (per .agents/rules/no-transient-project-ids-in-code.mdc). The follow-up landing the substantive defineConfig / defineContract / runtime() bodies will not need to change the type surface this scaffold publishes. ./family, ./migration, ./target are one-line re-exports from the upstream target / family packs (identical to the postgres facade); they ship as real, working surfaces today. tsconfigs, biome.jsonc, vitest.config.ts copied verbatim from the postgres facade. README mirrors the postgres facade's structure, drops the ./control / ./serverless sections, calls out the WebSocket-only transport and the placeholder status, leaves no transient-plan identifiers. architecture.config.json gains six glob entries (one per export file), placed beside the existing @prisma-next/postgres facade entries. No src/config/** or src/contract/** entries — those land when the follow-up introduces those directories. pnpm install regenerates pnpm-lock.yaml with one new workspace importer; the lockfile diff is contained to that block. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…-ppg-serverless
Replaces the placeholder runtime export in the facade with a substantive
factory ported from @prisma-next/postgres/src/runtime/postgres.ts.
The port preserves the lazy-runtime / closure-cached driver / on-demand
connect state machine that the long-lived TCP facade uses, while dropping
the surface that does not apply to the PPG WebSocket transport:
- Driver swap: @prisma-next/driver-postgres/runtime is replaced by
@prisma-next/driver-ppg-serverless/runtime, picking up the binding
shape and execute / query / executePrepared / acquireConnection
semantics the WebSocket driver settled earlier in this branch.
- @prisma/ppg becomes a direct workspace-catalog dependency. The facade
publishes a Client-typed ppgClient field on its options surface, so
the type must be reachable. The forbidden-dep manifest gate (pg,
@types/pg) still passes.
- PpgServerlessBinding has two variants, { kind: 'url' } and
{ kind: 'ppgClient' }, instead of the TCP facade's three. PPG handles
pooling on the wire side and there is no Pool object to bind to.
- toRuntimeBinding becomes a pass-through. For { kind: 'url' } the
facade forwards directly to the driver (no Pool wrapping, no
connectionTimeoutMillis / idleTimeoutMillis options). The
poolOptions block is removed from PrismaPostgresServerlessOptions.
- driver.create() is called with no argument. PPG's driver descriptor
takes no create-time options today (no cursor mode to configure).
- close() collapses to a state flip + a swallowed pending-connect await.
There is no facade-owned Pool to end(); the underlying driver owns its
own teardown.
- The transaction() body uses the same Object.assign(Object.create(txCtx),
...) prototype-preserving pattern as the TCP facade. The comment on
that pattern is preserved verbatim because the live invalidated getter
it protects is the same shape.
The previous placeholder type PpgServerlessFacadeBinding is removed; the
real PpgServerlessBinding is published from src/runtime/binding.ts and
re-exported through src/exports/runtime.ts. exports/runtime.ts also
re-exports the public client / options / transaction-context types so
callers can write strict type annotations against the published surface.
src/runtime/ is a new directory; architecture.config.json gains one
glob entry covering it (domain: extensions, layer: adapters, plane:
runtime). pnpm install adds @prisma/ppg to the lockfile's importer
block for the new package.
src/exports/config.ts and src/exports/contract-builder.ts remain
stubs that throw at call time. Filling them in is a follow-up; the
shape-parity goal of this commit is on ./runtime, not those.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… seam
Adds 20 smoke tests across two files covering the facade boundary
introduced in the prior commit.
prisma-postgres-serverless.test.ts (19 tests, vi.mock-heavy)
- Mirrors the @prisma-next/postgres facade-test pattern: every
external module (sql-runtime, sql-builder, sql-orm-client,
framework-components, target/adapter/driver runtimes) is mocked
via vi.mock, so each test asserts a wiring shape rather than a
full execution path.
- Covers: construction with { contract } and { contractJson };
static surface shape (sql / orm / raw / context / stack / connect /
runtime / transaction / prepare / close / Symbol.asyncDispose);
eager-sql vs lazy-driver split; runtime memoisation; driver.create
called with no argument; URL / ppgClient / explicit-binding routing
to the driver; URL validation (empty + non-postgres schemes);
multiple-binding rejection; double-connect rejection; missing-binding
rejection; transaction delegation to withTransaction; transaction
context exposes rebound sql + orm; close idempotence; asyncDispose
delegates to close; close before connect is a no-op; close while a
lazy connect is mid-flight resolves cleanly.
prisma-postgres-serverless.e2e.test.ts (1 test, real driver)
- Composes the actual driver-ppg-serverless / target-postgres /
adapter-postgres / sql-runtime stack and binds a hand-built fake PPG
Client through the { ppgClient } binding. Asserts the facade
constructs, connects through the real driver, and closes cleanly
through the real driver instance. Deliberately does not duplicate
the row-mapping coverage already exhaustively tested in the driver
package; the seam this test defends is the facade<->driver
composition, not the driver's internal lifecycle.
test/_fakes.ts is a slim local copy of the driver package's fake
Client / Session / Resultset surface, with only the probes the e2e
test actually reads (no per-session query history, no close-count
counter, no transaction-statement shortcut helper). Cross-package
test-utility imports are not conventional in this codebase, so the
local copy matches the pattern the long-lived TCP facade uses.
Combined gate set: typecheck, build, lint, lint:deps, manifest
hygiene, transient-ID regex, prose-attribution sweep, and bare-cast
scan are all green.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Adds a required `ppgUrl` field on DevDatabase, populated from `server.ppg.url` of @prisma/dev's programmatic server, normalised through the same helper as `connectionString`. Forward-compatible scaffolding. As of @prisma/dev@0.24.7 this endpoint serves the Prisma Accelerate / data-proxy GraphQL protocol, not the @prisma/ppg raw-SQL protocol consumed by @prisma-next/driver-ppg-serverless; the JSDoc on the field documents the protocol mismatch in-place. The field is surfaced so future @prisma/dev versions (or a test-utils-side PPG-protocol shim) can be wired in without further API churn. Companion to the ppg-serverless project; full context in projects/ppg-serverless/learnings.md. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Captures the Drive ceremony work that produced commits 002a5f8 / 06ace6b (Slice 2), bca9754 / 6f2bd64 / ad7b462 (Slice 3), 4e6f6f0 (Slice 4), 411b0d0 / 51e2666 (Slice 5). Slice 6 spec/plan included with a STATUS:HALTED banner at the top; the slice's central premise (@prisma/dev's server.ppg.url is PPG-compatible) was empirically and source-level falsified during D1. Full root-cause, options surfaced, and operator's choice (defer + draft PR + reconsider shim later) recorded in projects/ppg-serverless/learnings.md. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
- Bump version 0.11.0 → 0.12.0 and matching workspace:0.12.0 refs to align with the rest of the rebased workspace at v0.12.0. - Add the typescript peer-dependency declaration the workspace-wide lint gate requires (peerDependencies + peerDependenciesMeta.optional), missed when the package was scaffolded by copying driver-postgres before that rule landed. - Drop the stale src/named-cursor.ts coverage exclusion (copy artefact; file does not exist in this package). - Add direct tests for the bound impl that the unbound wrapper hides from the public surface (DRIVER.CLOSED guards on acquireConnection / acquireSession, the connect() misuse throw, the ownsClient accessor) and a row-mapper sparse-columns defensive-branch test. Lifts driver coverage from 89.13 % branches / 89.65 % funcs to 97.82 % / 100 %. - Regenerate pnpm-lock.yaml to reflect the workspace:0.12.0 specifier bumps (11-line mechanical change, no transitive churn). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…facade
Wires the existing @prisma-next/postgres control surface through the
serverless package family so consumers get one-import parity with the
non-serverless postgres facade. Pure forwarding boilerplate; no new
control driver is introduced.
Driver layer (@prisma-next/driver-ppg-serverless):
- New ./control entry re-exporting @prisma-next/driver-postgres/control.
Uses `export *` + `export { default }` because driver-postgres/control
ships both a named PostgresControlDriver class and a default-export
descriptor; preserving both surfaces.
- Adds @prisma-next/driver-postgres to dependencies; threads the new
entry through package.json exports + tsdown entry.
Facade layer (@prisma-next/prisma-postgres-serverless):
- ./config and ./contract-builder swap their call-time-throwing stubs
for `export * from "@prisma-next/postgres/*"`. The bespoke
PrismaPostgresServerlessConfigOptions interface is dropped; consumers
pick up the real PostgresConfigOptions / defineContract surface. The
stub always threw at runtime, so no concrete consumer can have
depended on the type.
- New ./control entry mirroring the same pattern (export * suffices
because postgres/control has no default export).
- Adds @prisma-next/postgres to dependencies. The implied transitive pg
dep is intentional; bundler tree-shaking keeps dist/runtime.mjs
edge-clean (verified: 0 pg imports in the built driver runtime).
Lockfile: +25 lines, all workspace-link additions.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… API
Real-cloud integration test that proves the facade's ORM round-trips
through the real PPG WebSocket wire protocol end-to-end against a
freshly-provisioned Prisma Postgres database. Every other test in the
facade and driver packages mocks the PPG client at the Client.newSession
boundary; wire-level serialization, auth, and WS framing have no
coverage there until this lands.
Lifecycle per run:
- beforeAll: POST /v1/projects via @prisma/management-api-sdk, then
apply the contract via the facade's ./control surface (TCP path;
the control plane is TCP-only by design — the facade's ./control
re-exports @prisma-next/postgres/control). dbInit requires a
migrationsDir on disk even for a from-scratch apply, so an empty
temp dir is created with mkdtemp; the planner generates the
create-from-scratch operations directly from the contract.
- it × 3: INSERT+SELECT via db.orm.Item, transaction commit, and
transaction rollback. All through db.orm.<Model> and
db.transaction(fn); no raw SQL strings in the test body.
- afterAll: close the facade, drop the temp migrationsDir, DELETE
/v1/projects/{id}. Teardown is best-effort; cleanup failures log a
manual-recovery breadcrumb rather than fail the suite.
The contract uses field.id.uuidv7() (the canonical workspace generated
-id preset) plus field.text() for the name column. The SQL ORM's
CreateInput currently requires explicit ids even when the contract has
a runtime execution default, so the test passes randomUUID() ids
inline — same pattern as the workspace's
collection-mutation-defaults integration test.
Skipped silently when PRISMA_POSTGRES_SERVICE_TOKEN is unset (local
development, fork PR runs); the workflow YAML's require-token step
hard-fails own-repo PR runs that are missing the secret. Verified
locally: the suite reports as 3 tests skipped with no token in env.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…rified
Two changes that hang together as the D3 Phase 2 fixup; both required
for the cloud integration test to pass end-to-end.
1. Driver: register array-OID parsers at PPG client construction.
`@prisma/ppg@1.0.1`'s `defaultClientConfig` ships parsers for scalar
OIDs only — no entries for `_text` (1009), `_int4` (1007), `_jsonb`
(3807) and so on. The framework adapter layer assumes the driver
hydrates `text[]` columns as JS arrays (matching `pg`'s native
behaviour); the adapter at packages/3-targets/6-adapters/postgres/
src/core/adapter.ts:99 literally banks on this. The framework's own
contract-marker read (`invariants text[]`) was the first place the
gap manifested: "Invalid contract marker row: invariants must be an
array (was string)".
Adds `withArrayParsers` — a helper that lifts a scalar-only
`ValueParser[]` into one that also handles the array variants of
every scalar OID present (covering bool, int2/4/8, float4/8, text,
varchar, json, jsonb). The driver wires it into
`createBoundDriverFromBinding`'s URL branch; the helper is
re-exported from `./runtime` so users supplying their own
`ppgClient` can opt in. 10 new unit tests at the `ValueParser`-
contract boundary. Driver test count: 77 → 87.
`postgres-array: 2.0.0` added to the workspace catalog as a new
driver dep. Pure-JS, edge-safe (no Node-only APIs), already a
transitive dep of `pg` — does not violate NFR1 (Workers/Edge/Deno/
Bun compatibility) or NFR2 (no new pg/pg-cursor/@types/pg).
2. Integration test: switch to PPG-compatible endpoint + carry D1
keepers.
The first Phase-2 attempt read `endpoints.accelerate.connectionString`
on the Management API response — but that field is the Accelerate /
data-proxy GraphQL URL form (`prisma+postgres://accelerate.prisma-
data.net/?api_key=…`), NOT a PPG URL. PPG itself rejects the scheme
upstream of any facade-level validation (`parseConnectionString` in
@prisma/ppg/dist/index.js:908). The PPG-compatible URL is on
`endpoints.pooled.connectionString` (the `postgres://identifier:key
@db.prisma.io:5432/…` form per the PPG docs). Switched the lookup;
updated the comment to call out the URL-scheme aliasing trap
explicitly (same pattern as the D6 falsification).
Also lands two earlier-authored D3 corrections:
- SDK 1.35.0 typegen drift: live response carries one
`connections[0]` with all endpoint variants under
`endpoints.{direct,pooled,accelerate}`, not multiple records keyed
by `kind`.
- TCP-gateway warm-up retry: ~5s window after `POST /v1/projects`
during which `pg.Client.connect` transient-rejects with "Failed
to connect to upstream database". Added `retryWithBackoff` plus
`isGatewayWarmupError` around `dbInit`.
And the D1 keeper infra that was sitting in the worktree:
- pnpm-workspace.yaml: `@prisma/management-api-sdk: 1.35.0` catalog
pin.
- test/integration/package.json: devDeps for the SDK + the facade
+ the postgres family/migration/runtime/target.
- .github/workflows/ci.yml: workflow env-var wiring + a "Require
PPG service token" step that hard-fails own-repo PR runs without
the secret.
Live verification (3/3 PASS, ~21s wall-clock):
- round-trips INSERT and SELECT through the ORM
- commits a transaction
- rolls back a transaction on thrown error
Suite skips silently locally and on fork PRs when
PRISMA_POSTGRES_SERVICE_TOKEN is unset.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ase 2
Project artifact updates for the D3 Phase 2 fixup that just landed in
the preceding commit. Two functional groups:
1. Slice 6 dispatch briefs now committed (previously untracked):
- slices/06-…/dispatches/02-control-reexports.md
- slices/06-…/dispatches/03-integration-test-rewrite.md
Both authored mid-flight as the original D1 plan was restructured
after the D6 falsification (in-process @prisma/dev — Accelerate,
not PPG) and the operator-chosen cloud-PPG path.
2. Doc accretion for the multi-layer Phase 2 discovery:
- spec.md: FR1 amended to record the driver's array-OID parser-
registration responsibility (gap surfaced by Phase 2 live run).
FR3 rewritten to document the URL-scheme aliasing trap:
prisma+postgres:// is Accelerate, not PPG; the PPG-compatible
URL is on endpoints.pooled.connectionString. D4 + D6 wording
already updated during earlier D3 phases now carries forward.
- slices/06-…/spec.md + slices/06-…/plan.md: status banners
finalised; D1 HALTED kept as historical record; D2/D3/D4
dispatch structure laid out.
- learnings.md: new "Slice 6 / D3 / Phase 2" entry capturing the
multi-layer wire-compat gap (facade-validator misdiagnosis
correction + driver array-parser gap). Generalisable lesson:
wire-compat gaps in driver substitutes are invisible to mocked
tests by definition; integration coverage against the real wire
is a slice-DoD prerequisite, not a project-DoD nice-to-have.
No code or test changes in this commit. Strictly project-folder
accretion.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…tion
Replaces the early-slice placeholder shells on both packages now that
the driver and facade are shipping (substantive `./runtime` on both,
`./control` re-exporting `@prisma-next/driver-postgres/control`,
cloud ORM round-trip verified end-to-end).
Driver README:
- Architecture diagram showing both binding paths (URL → driver-owned
PPG client with auto-registered array parsers vs caller-supplied
PPG client) terminating in either one-shot or long-lived
`Client.newSession()` calls over WebSocket.
- Usage block covering both `{ url }` and `{ ppgClient }` bindings;
the `ppgClient` example shows how to call `withArrayParsers` so
array-typed columns surface as JS arrays.
- Documents the URL form trap explicitly: the
`prisma+postgres://accelerate.prisma-data.net/?api_key=…` form is
Accelerate / data-proxy GraphQL, NOT PPG; PPG consumes the
`postgres://identifier:key@db.prisma.io:5432/…` form (the
Management API's `endpoints.pooled.connectionString`).
- Notes runtime-environment compatibility (Node 20+, Cloudflare
Workers, Vercel Edge, Deno, Bun).
- Section on the `./control` re-export: pulls `pg` into the install
graph but never into the runtime bundle; bundlers tree-shake the
unimported re-export from the runtime entry.
Facade README:
- Drops the "Placeholder facade" callout.
- Quick Start with `defineConfig` + module-scope client construction.
- Full Cloudflare Workers example mirroring the structure of the
`@prisma-next/postgres` README's edge example.
- Exports table updated: `./runtime` is substantive;
`./config`/`./contract-builder`/`./control` are real re-exports of
`@prisma-next/postgres/*` counterparts (the earlier stubs are
gone).
- Binding-variants section with all three input shapes
(`{ url }`, `{ ppgClient }`, `{ binding }`); ppgClient example
shows the `withArrayParsers` opt-in.
- Transactions section with the `db.transaction(fn)` callback
semantics.
- Runtime-environments + dual-plane (runtime over WS / control over
TCP) story.
Package-Layering.md:
- New entries in both the 3-targets tree (`7-drivers/ppg-serverless`,
multi-plane: runtime + migration) and the 3-extensions tree
(`postgres/` plus `prisma-postgres-serverless/`; the long-lived
facade was also missing from this doc, fixing while in the
neighborhood).
- New row in the "Drivers (Runtime Plane)" prose list for the PPG
driver, calling out the `./control` re-export shape.
- Both new packages added to the Directory → Published Package Name
reference table (and `@prisma-next/postgres`, which was also
missing).
No code changes. All shipping content is free of transient project
IDs per the always-apply rule (canonical regex returns empty over
both READMEs). Build / typecheck / lint / lint:deps green for both
packages; existing tests still pass (driver 87/87, facade 20/20).
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ose-out Two close-out-prep edits landing together: 1. `drive/calibration/dod.md`: new Project-DoD overlay item "Substrate-substitution wire-compat coverage". Records the load-bearing lesson surfaced during the ppg-serverless project's Phase 2 live verification: when a project introduces a new driver/adapter/runtime substrate that claims wire-compat parity with an existing one, live-wire integration coverage against the substituted backend is a slice-DoD prerequisite, not a project-DoD nice-to-have. Mocked-driver tests cannot see column-hydration, error-shape, or protocol-framing gaps at the wire boundary by construction — they shape the row themselves before it crosses the seam. The framework adapter often banks on per-column behaviour (e.g. `pg`'s native `text[]` -> JS-array hydration) that mocked tests trivially satisfy but the new substrate may not. Land the live-wire test in the slice that introduces the substrate, not in a later validation slice; otherwise wire-compat regressions surface only at close-out (or first real-user contact) when the cost of pivoting design is highest. 2. `test/integration/test/prisma-postgres-serverless/ cloud-integration.test.ts`: strip the inline comment's reference to `projects/ppg-serverless/learnings.md` (about to be deleted) and to the transient project ID "D1" (always-apply rule violation that slipped through). The comment's URL-scheme-aliasing explanation stands on its own without the pointer; the same guidance is now in the driver/facade READMEs and the project spec FR3, which are the durable surfaces a future reader will reach for. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
All 6 slices merged on the working branch; project DoD met (8/8
acceptance criteria PASS); the load-bearing methodology lesson
("substrate-substitution wire-compat coverage") landed in
`drive/calibration/dod.md` as a Project-DoD overlay item in the
preceding commit; the architectural design content the project
produced (driver structure, binding kinds, URL-form trap,
array-parser registration, `./control` re-export pattern) is now in
the substantive driver/facade READMEs that landed in commit
`704622736`; the one external reference to `projects/ppg-serverless/`
under `test/integration/` is stripped (preceding commit).
Per `drive-close-project § Step 4`, the contents of
`projects/ppg-serverless/` were classified as:
- spec.md, plan.md, slices/**, dispatches/**, learnings.md — transient
project artefacts (coordination scaffolding for the project run;
exist nowhere once it closes).
- design-notes.md — classified ambiguous → operator chose
reclassify-as-transient. The durable architectural content is
duplicated in the substantive READMEs; the "alternatives
considered + rejected" sections add historical context but no
actionable guidance, and the git log carries them for future
archaeology.
Open follow-ups to land as Linear tickets (out of scope for this PR):
- Upstream PPG: `defaultClientConfig` could register array parsers
itself, matching what `pg`'s built-in type registry does. If
accepted, our `withArrayParsers` becomes belt-and-suspenders
rather than load-bearing.
- `defineContract` factory-form does not propagate field-level
execution defaults to `CreateInput` type-level optionality.
Independent of ppg-serverless; surfaces on every facade. Fix is a
type-level change in `sql-orm-client/src/types.ts` but requires a
careful cross-facade audit.
- PPG free-tier per-PR project churn limits: if the
per-CI-run-provisions-a-fresh-project pattern hits free-tier
limits, consider a shared CI project with per-run database
isolation (still per-run isolation; less project churn).
- Weekly cleanup workflow that deletes leaked `pn-ci-*` projects
older than 24h, defensive against `afterAll` killed mid-execution.
- In-process PPG-protocol shim in `@prisma-next/test-utils` for
offline / no-network integration tests (option-a from the D6
falsification discussion; still viable later if cloud-test
maintenance becomes painful).
- `drive-qa-plan` script for `@prisma-next/prisma-postgres-serverless`
covering the facade's user-visible surface (manual-QA roll-up
deferred for this project per operator decision; cloud integration
test already exercises the same flow end-to-end automatically).
- Canonical transient-ID regex propagation into the dispatch brief
template and the implementer-persona pre-commit checklist (from
the Slice 2 / D1 / R1 transient-ID-in-JSDoc lesson; defer to
future-project close-out).
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ation duplicated by ppg + TS types The facade ships two layers of validation that duplicate work done elsewhere: - `validatePpgUrl` (trim + non-empty + `new URL(...)` + protocol check) duplicates `@prisma/ppg`'s `parseConnectionString`, which runs synchronously at `client(config)` construction time and produces strictly more informative errors (also rejects missing username/ password, which we did not). - The `providedCount !== 1` exclusive-one-of check duplicates the compile-time constraint already encoded by `PpgServerlessBindingInput`'s discriminated `?: never` fields. The runtime check only caught dynamic JS callers; the input is typed as `PpgServerlessBindingInput` everywhere, so anything reaching the helper has already been narrowed. Also drop the facade-local `PpgServerlessBinding` type alias and use the driver's exported `PpgBinding` directly — the facade was redefining a structurally-identical type and immediately passing it through to the driver. Public surface now re-exports `PpgBinding` (was `PpgServerlessBinding`) from `./runtime`. Verification: - `pnpm --filter @prisma-next/driver-ppg-serverless build` — green - `pnpm --filter @prisma-next/prisma-postgres-serverless typecheck` — green - `pnpm --filter @prisma-next/prisma-postgres-serverless test` — 17/17 pass - `pnpm --filter @prisma-next/driver-ppg-serverless test` — 87/87 pass - `pnpm lint:deps` — green Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…d impl The `#ownsClient` field, its constructor parameter, and the public `ownsClient` getter were threaded through `PpgServerlessBoundDriverImpl` in anticipation of close-time ownership branching that never materialised. No production code reads the getter: - The bound impl's own `close()` flips `#closed = true` and returns — ownership does not affect the body (the in-source comment explicitly notes PPG's `Client` has no `.close()` method, so there is nothing to release differentially). - The unbound wrapper's `close()` nulls the delegate and forwards to `delegate.close()` — never consults `delegate.ownsClient`. - No other driver in the tree tracks caller-vs-self ownership of the underlying transport client. The only references were two tautological getter assertions in `driver.bound-impl.test.ts` that exercised "constructor stores its arg". Removed alongside the field; the surrounding `describe` block goes with them. Verification: - `pnpm --filter @prisma-next/driver-ppg-serverless typecheck` — green - `pnpm --filter @prisma-next/driver-ppg-serverless build` — green - `pnpm --filter @prisma-next/driver-ppg-serverless test` — 85/85 pass - `pnpm --filter @prisma-next/prisma-postgres-serverless typecheck` — green - `pnpm --filter @prisma-next/prisma-postgres-serverless test` — 17/17 pass Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…p e2e wiring test Address inline review notes: 1. Drop the `url` / `ppgClient` shortcuts on the facade options and the `PpgServerlessBindingInput` / `PpgServerlessBindingFields` discriminated types that encoded them. Callers now pass `binding: PpgBinding` directly (a discriminated union the driver already exports). This deletes `src/runtime/binding.ts` entirely, the `resolvePpgServerlessBinding` / `resolveOptionalPpgServerlessBinding` helpers, and the local `type PpgClient = Client` alias. The `db.connect(...)` signature changes from `connect(input?: PpgServerlessBindingInput)` to `connect(binding?: PpgBinding)`. 2. Delete `prisma-postgres-serverless.e2e.test.ts`. No sibling facade (`@prisma-next/postgres`, `@prisma-next/sqlite`) ships a parallel `*.e2e.test.ts` alongside its mocked unit suite. The driver-package tests already cover the row-roundtrip path end-to-end against a fake PPG session; the integration-tests workspace covers the real-cloud path. Updated unit tests, the cloud-integration test, and the facade README to the new binding shape. Public exports drop `PpgServerlessBindingInput`; `PpgBinding` continues to be re-exported from the driver. Verification: - `pnpm --filter @prisma-next/prisma-postgres-serverless typecheck` — green - `pnpm --filter @prisma-next/prisma-postgres-serverless test` — 16/16 - `pnpm --filter @prisma-next/driver-ppg-serverless test` — 85/85 - `pnpm --filter @prisma-next/integration-tests typecheck` — green - `pnpm lint:deps` — green Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Removed JSDoc and inline comments that just restated method signatures or described what the next line does. Kept comments that carry non-obvious "why" content (PPG `Client` has no `.close()`; framework adapter expects hydrated arrays; `Object.create` instead of spread to preserve the live `invalidated` getter; TCP gateway warm-up window). Also removes the `// REVIEW: isn't that a clone of ppg type?` line that was accidentally committed earlier (`PpgBinding` is not a PPG-exported type — PPG only ships `ClientConfig` / `parseConnectionString` inputs, not a driver-side binding union). Verification: - `pnpm --filter @prisma-next/driver-ppg-serverless typecheck && build && test` \u2014 green (85/85) - `pnpm --filter @prisma-next/prisma-postgres-serverless test` \u2014 16/16 - `pnpm --filter @prisma-next/integration-tests typecheck` \u2014 green Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Replace the namespace-import + local-alias pattern (`import * as sqlBuilderModule … const sqlBuilder = sqlBuilderModule.sql`) with the named-import form (`import { sql as sqlBuilder }`), matching the postgres facade in `packages/3-extensions/postgres/src/runtime/postgres.ts`. The namespace pattern was an artifact; no test or runtime path depended on it.
Verification:
- `pnpm --filter @prisma-next/prisma-postgres-serverless typecheck` — green
- `pnpm --filter @prisma-next/prisma-postgres-serverless test` — 16/16
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…facade redefinition Promote the package-private `OrmClient` alias in `sql-orm-client/src/orm.ts` to a public type with a `Record<never, never>` default on its `Collections` parameter (matching `orm()`'s own signature default). Use it directly in the PPG facade instead of the `ReturnType<typeof ormBuilder<TContract>>` recovery pattern. Note: `packages/3-extensions/postgres/src/runtime/postgres.ts` carries the same redefinition (line 42) and is now stale-aligned. Migrating it is a separate follow-up; out of scope for this PR. Verification: - `pnpm --filter @prisma-next/sql-orm-client build` \u2014 green - `pnpm --filter @prisma-next/sql-orm-client test` \u2014 489/489, no typecheck errors - `pnpm --filter @prisma-next/prisma-postgres-serverless typecheck && test` \u2014 16/16 - `pnpm lint:deps` \u2014 green Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…int:casts ratchet
The biome `no-bare-cast` plugin matches `$x as $t` syntactically, which
catches `import { sql as sqlBuilder }` alongside the intended `value as T`
cast form. The two new import-rename sites my earlier commit introduced
tripped the per-PR ratchet (current=1340 merge-base=1338, delta=+2).
Import the original names (`sql` / `orm`) and rename the local result
constants to `sqlClient` / `ormClient` instead. The public return object
still exposes `db.sql` / `db.orm` via `{ sql: sqlClient, orm: ormClient }`.
The postgres facade has the same import-rename pattern in its baseline
and would benefit from the same treatment as a separate cleanup; not
this PR.
Verification:
- `pnpm --filter @prisma-next/prisma-postgres-serverless typecheck` \u2014 green
- `pnpm --filter @prisma-next/prisma-postgres-serverless test` \u2014 16/16
- `pnpm lint:casts` \u2014 delta=0
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
packages/3-extensions/prisma-postgres-serverless/test/prisma-postgres-serverless.test.ts (2)
17-51: 🏗️ Heavy liftReduce module-level mocks in this suite.
This suite is almost entirely wired through
vi.mock(...), which weakens confidence in facade wiring. Prefer direct imports and only fake the external boundary (@prisma/ppgbehavior) to keep tests closer to real runtime composition. As per coding guidelines:**/*.{test,spec}.{ts,tsx,js,jsx}: Prefer direct imports over module mocks in test files.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-extensions/prisma-postgres-serverless/test/prisma-postgres-serverless.test.ts` around lines 17 - 51, The test file over-mocks many internal modules which reduces confidence in real wiring; replace module-level vi.mock calls for internal pieces (e.g., instantiateExecutionStack, createExecutionContext, createSqlExecutionStack, createRuntime, withTransaction, sql, orm, PostgresContractSerializer.deserializeContract) with direct imports so the real implementations are exercised, and only keep a mock for the external boundary you control (the `@prisma/ppg-like` behavior); update the top of prisma-postgres-serverless.test.ts to import those symbols directly and remove the corresponding vi.mock(...) blocks, leaving a single targeted mock for the external adapter/driver interface.
212-212: ⚡ Quick winRemove broad whole-object casts in test setup.
Line 212 and Line 240 cast entire option objects; these casts are avoidable by typing local variables to the overload option type (or by using typed fakes), which keeps type checks useful.
As per coding guidelines: `**/*.{ts,tsx}`: Minimize type casts; never cast a whole object/class when casting one property would suffice.Suggested direction
- const db = prismaPostgresServerless({ - contract, - binding: { kind: 'ppgClient', client: fakeClient }, - } as unknown as Parameters<typeof prismaPostgresServerless<typeof contract>>[0]); + const options: Parameters<typeof prismaPostgresServerless<typeof contract>>[0] = { + contract, + binding: { kind: 'ppgClient', client: fakeClient as never }, + }; + const db = prismaPostgresServerless(options); - const db = prismaPostgresServerless({ - contract, - } as Parameters<typeof prismaPostgresServerless<typeof contract>>[0]); + const optionsNoBinding: Parameters<typeof prismaPostgresServerless<typeof contract>>[0] = { + contract, + }; + const db = prismaPostgresServerless(optionsNoBinding);Also applies to: 240-240
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-extensions/prisma-postgres-serverless/test/prisma-postgres-serverless.test.ts` at line 212, The test is using a broad whole-object cast when calling prismaPostgresServerless (cast to Parameters<typeof prismaPostgresServerless<typeof contract>>[0]); replace those whole-object casts by declaring a local variable typed to the actual options/overload type expected by prismaPostgresServerless (e.g., create const opts: Parameters<typeof prismaPostgresServerless<typeof contract>>[0] = { ... } or define a narrower mock type for the fake logger/database), populate only the needed properties, and pass that typed variable to prismaPostgresServerless; if only one property needs a cast, cast that single property rather than the entire options object.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/3-extensions/prisma-postgres-serverless/README.md`:
- Around line 205-208: The "Related Docs" links in
packages/3-extensions/prisma-postgres-serverless/README.md are using
"../../docs/..." and thus resolve incorrectly; update each link reference in the
README (the three lines that start with "Architecture:" and both "Subsystem:"
and "ADR:") to ascend three levels (use "../../../docs/...") so they point to
the repository-level docs paths instead of packages/docs.
In
`@packages/3-extensions/prisma-postgres-serverless/src/runtime/prisma-postgres-serverless.ts`:
- Around line 238-240: The thrown Error message in the connect() path
incorrectly references runtime(...); update the guidance text (the Error thrown
where connect() validates binding) to tell users to pass the binding via the
constructor options or call db.connect(binding) instead of mentioning
runtime(...). Locate the throw new Error(...) in the Prisma Postgres serverless
runtime (the code path inside connect()/connect validation) and replace the text
to reference the constructor options or connect(binding) API.
In `@packages/3-targets/7-drivers/ppg-serverless/src/exports/runtime.ts`:
- Around line 155-161: The check in runtime.ts inside async explain(request)
currently throws driverError('DRIVER.NOT_CONNECTED', USE_BEFORE_CONNECT_MESSAGE)
when delegate.explain is undefined even though `#requireDelegate`() already
ensured a connected driver; change this to throw a more accurate error
indicating explain is not supported (e.g.,
driverError('DRIVER.EXPLAIN_NOT_SUPPORTED', 'driver does not support explain')
or at minimum driverError('DRIVER.NOT_SUPPORTED', 'driver does not support
explain')), updating the error code string and message near the delegate.explain
undefined branch while keeping the callsite (async explain, `#requireDelegate`(),
and driverError invocation) intact.
In `@packages/3-targets/7-drivers/ppg-serverless/src/normalize-error.ts`:
- Around line 66-69: The helper isTransientWebSocketClosure currently treats
every non-1000/1001 close as transient; update it so it returns true only for
server/temporary closure codes (specifically 1006 when present, and 1011, 1012,
1013, 1014) and false for all others (including 1002, 1008, 1009, 1010); keep
the undefined -> false check and reference the isTransientWebSocketClosure
function to locate and replace the boolean logic accordingly.
In `@packages/3-targets/7-drivers/ppg-serverless/src/ppg-driver.ts`:
- Around line 22-27: Remove the inline biome-ignore and replace the empty
interface with a lint-clean placeholder member: update the
PpgServerlessDriverCreateOptions interface to include a reserved/commented
property (e.g. export interface PpgServerlessDriverCreateOptions { /** Reserved
for future codec options */ _reserved?: undefined }) so the interface is
non-empty while preserving future extension compatibility; reference
PpgServerlessDriverCreateOptions in ppg-driver.ts and delete the biome-ignore
comment.
In `@packages/3-targets/7-drivers/ppg-serverless/test/driver.transaction.test.ts`:
- Around line 48-76: The test "routes execute / query / executePrepared through
the same held session" claims to exercise executePrepared but doesn't call it;
either add a call to txn.executePrepared (and drain/assert its behavior and
include its SQL in the expected fake.sessionQueryHistory()) or rename the test
to reflect only "execute / query". Locate the test in driver.transaction.test.ts
around the block that uses connection.beginTransaction() and
txn.query()/txn.execute(), then either insert a prepared execution call using
txn.executePrepared(...) and update the expected history array to include its
SQL, or change the it(...) description to match the actual calls made.
In `@packages/3-targets/7-drivers/ppg-serverless/tsconfig.json`:
- Around line 3-6: The tsconfig currently sets compilerOptions.outDir to "dist"
which conflicts with bundler output; update the tsconfig.json compilerOptions to
emit to the repo-safe directory (e.g. "dist-tsc" or "dist-tsc-prod" as
appropriate) instead of "dist" so accidental tsc outputs do not collide with
bundler artifacts; locate the compilerOptions block (rootDir/outDir) in the
tsconfig.json for this package and change outDir accordingly.
In `@test/integration/test/prisma-postgres-serverless/cloud-integration.test.ts`:
- Around line 215-220: The test currently swallows all errors by attaching
.catch(() => {}) to the db.transaction call; replace that with an explicit
assertion that the transaction rejects with the expected error (e.g., assert the
promise returned by db.transaction(...) rejects with the 'intentional rollback'
error) so failure modes aren’t hidden; locate the db.transaction(...) block that
calls tx.orm.Item.create({ id: carolId, name: 'carol' }) and change the
call-site to assert rejection (for example using your test framework's
expect(...).rejects/throws) and optionally follow with a check that the Item
with carolId was not created to confirm rollback.
---
Nitpick comments:
In
`@packages/3-extensions/prisma-postgres-serverless/test/prisma-postgres-serverless.test.ts`:
- Around line 17-51: The test file over-mocks many internal modules which
reduces confidence in real wiring; replace module-level vi.mock calls for
internal pieces (e.g., instantiateExecutionStack, createExecutionContext,
createSqlExecutionStack, createRuntime, withTransaction, sql, orm,
PostgresContractSerializer.deserializeContract) with direct imports so the real
implementations are exercised, and only keep a mock for the external boundary
you control (the `@prisma/ppg-like` behavior); update the top of
prisma-postgres-serverless.test.ts to import those symbols directly and remove
the corresponding vi.mock(...) blocks, leaving a single targeted mock for the
external adapter/driver interface.
- Line 212: The test is using a broad whole-object cast when calling
prismaPostgresServerless (cast to Parameters<typeof
prismaPostgresServerless<typeof contract>>[0]); replace those whole-object casts
by declaring a local variable typed to the actual options/overload type expected
by prismaPostgresServerless (e.g., create const opts: Parameters<typeof
prismaPostgresServerless<typeof contract>>[0] = { ... } or define a narrower
mock type for the fake logger/database), populate only the needed properties,
and pass that typed variable to prismaPostgresServerless; if only one property
needs a cast, cast that single property rather than the entire options object.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: e220d8fd-58ec-43b8-ba6b-0379d31835e9
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (52)
.github/workflows/ci.ymlarchitecture.config.jsondocs/architecture docs/Package-Layering.mddrive/calibration/dod.mdpackages/3-extensions/prisma-postgres-serverless/README.mdpackages/3-extensions/prisma-postgres-serverless/biome.jsoncpackages/3-extensions/prisma-postgres-serverless/package.jsonpackages/3-extensions/prisma-postgres-serverless/src/exports/config.tspackages/3-extensions/prisma-postgres-serverless/src/exports/contract-builder.tspackages/3-extensions/prisma-postgres-serverless/src/exports/control.tspackages/3-extensions/prisma-postgres-serverless/src/exports/family.tspackages/3-extensions/prisma-postgres-serverless/src/exports/migration.tspackages/3-extensions/prisma-postgres-serverless/src/exports/runtime.tspackages/3-extensions/prisma-postgres-serverless/src/exports/target.tspackages/3-extensions/prisma-postgres-serverless/src/runtime/prisma-postgres-serverless.tspackages/3-extensions/prisma-postgres-serverless/test/_fakes.tspackages/3-extensions/prisma-postgres-serverless/test/prisma-postgres-serverless.test.tspackages/3-extensions/prisma-postgres-serverless/tsconfig.build.jsonpackages/3-extensions/prisma-postgres-serverless/tsconfig.jsonpackages/3-extensions/prisma-postgres-serverless/tsconfig.prod.jsonpackages/3-extensions/prisma-postgres-serverless/tsdown.config.tspackages/3-extensions/prisma-postgres-serverless/vitest.config.tspackages/3-extensions/sql-orm-client/src/exports/index.tspackages/3-extensions/sql-orm-client/src/orm.tspackages/3-targets/7-drivers/ppg-serverless/README.mdpackages/3-targets/7-drivers/ppg-serverless/biome.jsoncpackages/3-targets/7-drivers/ppg-serverless/package.jsonpackages/3-targets/7-drivers/ppg-serverless/src/core/array-parsers.tspackages/3-targets/7-drivers/ppg-serverless/src/core/descriptor-meta.tspackages/3-targets/7-drivers/ppg-serverless/src/core/row-mapper.tspackages/3-targets/7-drivers/ppg-serverless/src/exports/control.tspackages/3-targets/7-drivers/ppg-serverless/src/exports/runtime.tspackages/3-targets/7-drivers/ppg-serverless/src/normalize-error.tspackages/3-targets/7-drivers/ppg-serverless/src/ppg-driver.tspackages/3-targets/7-drivers/ppg-serverless/test/_fakes.tspackages/3-targets/7-drivers/ppg-serverless/test/array-parsers.test.tspackages/3-targets/7-drivers/ppg-serverless/test/driver.basic.test.tspackages/3-targets/7-drivers/ppg-serverless/test/driver.bound-impl.test.tspackages/3-targets/7-drivers/ppg-serverless/test/driver.connection.test.tspackages/3-targets/7-drivers/ppg-serverless/test/driver.errors.test.tspackages/3-targets/7-drivers/ppg-serverless/test/driver.transaction.test.tspackages/3-targets/7-drivers/ppg-serverless/test/driver.unbound.test.tspackages/3-targets/7-drivers/ppg-serverless/test/normalize-error.test.tspackages/3-targets/7-drivers/ppg-serverless/test/row-mapper.test.tspackages/3-targets/7-drivers/ppg-serverless/tsconfig.jsonpackages/3-targets/7-drivers/ppg-serverless/tsconfig.prod.jsonpackages/3-targets/7-drivers/ppg-serverless/tsdown.config.tspackages/3-targets/7-drivers/ppg-serverless/vitest.config.tspnpm-workspace.yamltest/integration/package.jsontest/integration/test/prisma-postgres-serverless/cloud-integration.test.tstest/utils/src/exports/index.ts
| "compilerOptions": { | ||
| "rootDir": ".", | ||
| "outDir": "dist" | ||
| }, |
There was a problem hiding this comment.
Keep TypeScript emit output off dist.
Setting outDir to dist overrides the repo safety default and can mix accidental tsc emits with tsdown bundle artifacts.
Suggested fix
{
"extends": ["`@prisma-next/tsconfig/base`"],
"compilerOptions": {
- "rootDir": ".",
- "outDir": "dist"
+ "rootDir": "."
},
"include": ["src/**/*.ts", "test/**/*.ts"],
"exclude": ["dist"]
}Based on learnings, the repo intentionally routes accidental tsc emits to dist-tsc/dist-tsc-prod to avoid collisions with bundler dist output.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "compilerOptions": { | |
| "rootDir": ".", | |
| "outDir": "dist" | |
| }, | |
| { | |
| "extends": ["`@prisma-next/tsconfig/base`"], | |
| "compilerOptions": { | |
| "rootDir": "." | |
| }, | |
| "include": ["src/**/*.ts", "test/**/*.ts"], | |
| "exclude": ["dist"] | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-targets/7-drivers/ppg-serverless/tsconfig.json` around lines 3 -
6, The tsconfig currently sets compilerOptions.outDir to "dist" which conflicts
with bundler output; update the tsconfig.json compilerOptions to emit to the
repo-safe directory (e.g. "dist-tsc" or "dist-tsc-prod" as appropriate) instead
of "dist" so accidental tsc outputs do not collide with bundler artifacts;
locate the compilerOptions block (rootDir/outDir) in the tsconfig.json for this
package and change outDir accordingly.
Apply 6 of 7 CodeRabbit comments on PR #695: 1. README `Related Docs`: fix relative paths from `../../docs/...` to `../../../docs/...` (README lives three levels deep under repo root). 2. Facade `connect()`: error message said "Pass binding to runtime(...)" but `runtime()` takes no arguments. Now points at `prismaPostgresServerless(...)` (the factory) and `db.connect(binding)`. 3. Driver `explain()`: was throwing `DRIVER.NOT_CONNECTED` with the "not connected" message when the bound driver simply does not implement `explain` (since `#requireDelegate()` has already confirmed connection). Switched to a new `DRIVER.EXPLAIN_NOT_SUPPORTED` code with a matching message, aligning with sqlite-driver's precedent. 4. `isTransientWebSocketClosure`: previously treated any code outside 1000/1001 as transient, which is wrong for protocol/policy/data errors (1002, 1003, 1007, 1008, 1009, 1010). Narrowed to the server-side / temporary codes (1006, 1011, 1012, 1013, 1014) per RFC 6455 semantics. 5. `PpgServerlessDriverCreateOptions`: dropped the `biome-ignore lint/suspicious/noEmptyInterface` suppression by switching from an empty interface to a type alias with a `?: never` placeholder field. Satisfies the AGENTS.md rule "Never suppress biome lints" and keeps the option-bag arity reserved. 6. Transaction test name: renamed "routes execute / query / executePrepared" to "routes execute and query" — the body only exercises the first two. 7. Cloud-integration rollback test: replaced bare `.catch(() => {})` with `expect(...).rejects.toThrow(...)` so the test fails if `withTransaction` no longer rethrows. Skipped: tsconfig `outDir: "dist"` warning. The override is consistent across all sibling driver and facade packages; fixing only the two new ones would create an island. Worth a repo-wide cleanup but out of this PR's scope. Verification: - `pnpm --filter @prisma-next/driver-ppg-serverless typecheck && test` \u2014 85/85 - `pnpm --filter @prisma-next/prisma-postgres-serverless typecheck && test` \u2014 16/16 - `pnpm --filter @prisma-next/integration-tests typecheck` \u2014 green - `pnpm lint:casts` \u2014 delta=0 - `pnpm lint:deps` \u2014 green Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
CI Coverage job hit 90.91% branches on `ppg-driver.ts` (threshold: 95%)
after my earlier test-trim commits inadvertently dropped the only test
that exercised the `{ kind: "url" }` binding branch in
`createBoundDriverFromBinding`, and the new RFC-6455 `switch` in
`isTransientWebSocketClosure` left several cases unexercised.
Fixes:
1. Restore a `createBoundDriverFromBinding` URL-binding test in
`driver.bound-impl.test.ts` (constructs the bound impl from a URL
binding without going to network; PPG's `defaultClientConfig` is
pure, and `client(config)` validates the URL synchronously before
any I/O). Asserting `bound.state === "connected"` also covers the
pre-close branch of the `state` getter.
2. Add table-driven tests in `normalize-error.test.ts` for each
RFC-6455 transient code (1006, 1012, 1013, 1014) and a representative
set of permanent codes (1002, 1003, 1008, 1009). These were unwritten
before — the previous `code === undefined || code === 1000 || code === 1001
? false : true` test covered only the 1011 → transient case.
3. Annotate the `config.parsers ?? []` fallback at the
`createBoundDriverFromBinding` URL branch with `/* v8 ignore next */`.
PPG's `defaultClientConfig` always populates `parsers`; the `??`
exists only because `ClientConfig.parsers` is typed as optional. The
defensive fallback stays as runtime cover, but the branch is honestly
marked as unreachable in practice rather than tested with fake input
(the earlier attempt to push the fallback into `withArrayParsers` so
it could be tested via `withArrayParsers(undefined)` was just moving
the dead branch to a different file).
Driver coverage now reports 100% statements / branches / functions /
lines.
Verification:
- `pnpm --filter @prisma-next/driver-ppg-serverless test --coverage` \u2014 94/94, all 100%
- `pnpm --filter @prisma-next/prisma-postgres-serverless test` \u2014 16/16
- `pnpm lint:casts` \u2014 delta=0
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Linked issue
Refs TML-2731.
At a glance
Before this PR, the only Postgres path was
@prisma-next/postgresbacked bypgover TCP — which doesn't load on Cloudflare Workers, Vercel Edge, Deno Deploy, or browsers.Decision
Ship two new packages so prisma-next runs on edge runtimes against Prisma Postgres:
@prisma-next/driver-ppg-serverless(packages/3-targets/7-drivers/ppg-serverless/) — wraps@prisma/ppg's WebSocket transport behind the SQL driver seam. DeclaresfamilyId: 'sql',targetId: 'postgres'so the existingtarget-postgres+adapter-postgresare reused unchanged.@prisma-next/prisma-postgres-serverless(packages/3-extensions/prisma-postgres-serverless/) — sibling facade to@prisma-next/postgres. Composes the same target + adapter with the new driver and re-exportsconfig/contract-builder/control/family/migration/targetfrom@prisma-next/postgresso users get the same one-import experience.Per-environment facade asymmetry is the framing decision — Node-process and edge get separate facades that share authoring + ORM but differ in transport (see ADR 207).
How it fits together
@prisma/ppgin the workspace catalog. Early-access dependency — breakage must surface at upgrade time (seepnpm-workspace.yaml).execute/query/executePreparedopen a freshClient.newSession()per call.executePreparedcollapses toexecute(PPG has no first-class prepare; params are already safely parameterised). PPG'sDatabaseError/WebSocketError/ValidationErrortranslate to the sameSqlQueryErrorshapes thatdriver-postgresproduces, so middleware and user error handling don't have to branch on driver. Seepackages/3-targets/7-drivers/ppg-serverless/src/ppg-driver.tsandpackages/3-targets/7-drivers/ppg-serverless/src/normalize-error.ts.acquireConnection()opens a reusable PPG session the caller can hold across operations;beginTransaction()issuesBEGIN/COMMIT/ROLLBACKon the held session. Shared between top-level and long-lived paths via an internalPpgServerlessQueryableabstract.url/ppgClient/ explicitbinding), constructs the runtime once, and memoises it. Authoring and ORM surfaces (sql,orm,context) are immediate so users can calldb.orm.Xwithout an explicitconnect(). Seepackages/3-extensions/prisma-postgres-serverless/src/runtime/prisma-postgres-serverless.ts.'{a,b,c}');pgnatively hydrates them to JS arrays, and the framework's adapter layer banks on the JS-array shape (the contract-marker readinvariants text[]is the first place this manifests). The driver registers parsers for the array-OID variants (_bool,_int2,_int4,_int8,_float4,_float8,_text,_varchar,_json,_jsonb) of every scalar OID PPG'sdefaultClientConfigalready parses. ThewithArrayParsershelper is exported so callers providing their ownppgClientcan opt in. Seepackages/3-targets/7-drivers/ppg-serverless/src/core/array-parsers.ts../controlsurface, then round-trips INSERT + SELECT, an explicitCOMMIT, and an explicitROLLBACKthrough the WebSocket data plane. Seetest/integration/test/prisma-postgres-serverless/cloud-integration.test.ts.Reviewer notes
dbInit/dbVerifyaren't edge workloads. The driver's./controlre-exports@prisma-next/driver-postgres/controland the facade's./controlre-exports@prisma-next/postgres/control— both pullpginto the install graph but bundlers tree-shake them out of the runtime entry. This avoids shipping a second control driver just to be wire-compatible with PPG.postgres://identifier:key@db.prisma.io:5432/postgres?sslmode=require. Theprisma+postgres://accelerate.prisma-data.net/?api_key=…form returned by Accelerate / data-proxy shares the URL scheme but carries a different wire protocol (GraphQL over HTTPS); PPG rejects it upstream of the facade. We deliberately do not second-guess the URL shape — PPG produces the precise error. If you provision via the Management API, take the URL fromendpoints.pooled.connectionString(notendpoints.accelerate.connectionString).@prisma/dev'sserver.ppg.url? The original plan was to run the integration test in-process against@prisma/dev. Falsified during slice 6: at upstream0.24.7that endpoint serves the Accelerate GraphQL protocol, not PPG's raw-SQL/v0/statement+/v0/sessionprotocol — same URL-scheme-aliasing pitfall as above. Pivoted to real-cloud integration via the Management API. Thetest-utilschange (test/utils/src/exports/index.ts) still surfacesserver.ppg.urlasDevDatabase.ppgUrlfor future use once upstream serves the matching protocol.drive/calibration/dod.md): when a project introduces a new driver/adapter/runtime substrate claiming wire-compat parity, live-wire coverage against the substituted backend is a slice-DoD prerequisite, not a project-DoD nice-to-have..github/workflows/ci.ymlhard-fails own-repo PR runs missingPRISMA_POSTGRES_SERVICE_TOKEN; fork PRs silently skip viadescribe.skipIfin the test. Without the gate, the cloud test would silently no-op on own-repo runs and the integration coverage would go unenforced. Repo secret needs to be configured before this PR is merged.Signed-off-by:trailers; all 24 commits in the range were retroactively signed during PR prep viagit rebase --signoff. SHAs differ from earlier review pushes if you held a local checkout.Compatibility / migration / risk
@prisma-next/postgresusers — this is a sibling facade, not a replacement.pnpm lint:depsstays green.architecture.config.jsonregisters the new domain/layer placements (driver undertargets / drivers, facade underextensions / adapters);docs/architecture docs/Package-Layering.mdupdated to match.pg,pg-cursor,pg-pool, or@types/pgin the driver'sdependencies— only@prisma/ppg. Verified by import-lint.Verification
pnpm build— green.pnpm test:packages— driver suites:array-parsers,driver.basic,driver.bound-impl,driver.connection,driver.errors,driver.transaction,driver.unbound,normalize-error,row-mapper(9 spec files). Facade suites:prisma-postgres-serverless+prisma-postgres-serverless.e2e(2 spec files).pnpm test:integration— cloud-integration round-trip against a real PPG database provisioned per run (3 cases: SELECT/INSERT, COMMIT, ROLLBACK).pnpm lint:deps— green.Skill update
n/a — internal only (new packages follow the existing facade + driver patterns; no change to user-facing CLI / config / glossary).
Follow-ups
defaultClientConfigcould register array parsers itself, matchingpg's built-in type registry. If accepted, ourwithArrayParsersbecomes belt-and-suspenders rather than load-bearing.defineContractfactory-form doesn't propagate field-level execution defaults toCreateInputtype-level optionality. Independent of this PR; surfaces on every facade. Fix is a type-level change insql-orm-client/src/types.ts— separate Linear ticket.@prisma/devserves the PPG raw-SQL protocol atserver.ppg.url, swap the cloud-integration test for an in-process variant (theDevDatabase.ppgUrlplumbing already exists; just unused today).Alternatives considered
/v0/statement) in addition to WebSocket — rejected. Transactions need a stateful session; supporting both transports would double the driver's lifecycle complexity for no user-visible win. The driver is WebSocket-only and opens a fresh session per top-level call.dbInit/dbVerifyaren't edge workloads. Re-exporting the existing TCPpostgrescontrol surface ships the same control plane to both facades with zero new code.@prisma-next/postgresbehind a/serverlesssubpath — rejected per ADR 207. A separate facade signals deployment-lifecycle intent at the import line; one facade with two subpaths conflates them. The new facade also deliberately omits./serverlessitself — the package name already signals the lifecycle.pg-cursor-equivalent streaming option — dropped. PPG'sCollectableIteratorstreams row-by-row natively; the existing driver'scursor: { batchSize }knob has no PPG analogue and would be a fake surface.@prisma/dev'sserver.ppg.url, falsified empirically (different wire protocol at the same URL scheme). Pivoted to real-cloud integration via the Management API; gated on a repo secret so fork PRs skip silently.Checklist
git commit -s) per the DCO. All 24 commits carrySigned-off-by:trailers after the prep rebase.TML-NNNN: <sentence-case title>form.n/a — internal only).Summary by CodeRabbit
New Features
Documentation
Tests
Chores