Skip to content

fix(postgres-js): don't override Date serializer, only the parser (#5789)#5976

Open
momomuchu wants to merge 1 commit into
drizzle-team:mainfrom
momomuchu:fix/postgres-js-date-serializer-5789
Open

fix(postgres-js): don't override Date serializer, only the parser (#5789)#5976
momomuchu wants to merge 1 commit into
drizzle-team:mainfrom
momomuchu:fix/postgres-js-date-serializer-5789

Conversation

@momomuchu

@momomuchu momomuchu commented Jul 2, 2026

Copy link
Copy Markdown

Summary

drizzle() for the postgres-js driver overrides postgres.js's outbound timestamp/date serializers alongside its inbound parsers, when only the parsers were meant to be overridden (see porsager/postgres#761). This breaks every ${jsDate} parameter passed through a raw sql template, throwing TypeError [ERR_INVALID_ARG_TYPE] from postgres.js's Bind() step.

Closes #5789

Root cause

src/postgres-js/driver.ts, in construct():

for (const type of ['1184', '1082', '1083', '1114', '1182', '1185', '1115', '1231']) {
  client.options.parsers[type as any] = transparentParser;
  client.options.serializers[type as any] = transparentParser; // <- rebinds outbound serializer too
}

postgres.js's default serializer for OID 1184 is x => (x instanceof Date ? x : new Date(x)).toISOString(). After this override the passthrough returns the raw Date unchanged; postgres.js's Bind() then calls Buffer.byteLength(date), which throws.

Fix

Drop the serializer override from the date-OID loop; keep only the parser override (the documented intent per porsager/postgres#761). The unrelated json/jsonb serializer overrides (OIDs 114/3802) are untouched.

Alternative considered and rejected: keep a serializer override but make it "actually serialize" (x => x instanceof Date ? x.toISOString() : x). Rejected, it duplicates postgres.js's own default Date serializer and risks drifting from its implementation. Dropping the override and falling back to postgres.js's default is narrower.

Tests

  • tests/postgres-js/driver.test.ts (new): source-level unit test against the real construct() using an unconnected postgres.js client (no live DB, postgres.js only opens a socket on first query). Asserts the outbound serializer for OID 1184 still matches postgres.js's default and produces a wire-safe string, while the inbound parser stays neutralized. RED (before): 1 failed / 1 passed → GREEN (after): 2 passed.
  • integration-tests/tests/pg/postgres-js.test.ts (new case, insert via raw sql with a Date param does not throw (#5789)): end-to-end reproduction against a timestamptz column. This test requires a live Postgres and was not executed in my environment, included for CI/reviewer verification.

Verification run locally

  • vitest run (drizzle-orm package): 568/568 passed
  • tsc -p tests/tsconfig.json --noEmit (strict): 0 errors
  • dprint check + eslint on changed files: clean

…izzle-team#5789)

drizzle(client) neutralized both the inbound parsers AND the outbound
serializers for date/timestamp OIDs (1184/1082/1083/1114/1182/1185/1115/1231).
Only the parser override was intended (see porsager/postgres#761): it lets
Drizzle parse raw text from Postgres itself. The serializer override was
unintentional collateral damage: it made postgres-js's Bind() step receive a
raw JS `Date` instance instead of a wire-safe string for any `${jsDate}`
parameter passed through a raw `sql` template, which throws
`TypeError [ERR_INVALID_ARG_TYPE]` from `Buffer.byteLength(date)`.

The fix drops the `client.options.serializers[type] = transparentParser`
assignment from the date-OID loop, leaving only the parser override. The
`json`/`jsonb` serializer overrides just below (unrelated OIDs 114/3802) are
untouched.

Alternative considered and rejected: keep a serializer override but replace
the passthrough with a real serializer (`x => x instanceof Date ? x.toISOString() : x`).
Rejected because it duplicates postgres-js's own default Date serializer
logic for no benefit, dropping the override entirely lets postgres-js's
already-correct default serializer do the job, which is the narrower and
more maintainable change (no drift risk if postgres-js changes its own
serializer implementation).

Adds a source-level regression test (drizzle-orm/tests/postgres-js/driver.test.ts)
that exercises the real driver.ts `construct()` against an unconnected
postgres-js client (postgres-js is lazy, so no live DB is needed) and asserts
the outbound serializer for OID 1184 still matches postgres-js's own default
and produces a wire-safe string, while the inbound parser is still
neutralized. Also adds an integration-level regression test
(integration-tests/tests/pg/postgres-js.test.ts) reproducing the exact
reported scenario end-to-end (raw `sql` insert with a Date param against a
timestamptz column); this test requires a live Postgres instance and was not
executed locally.

Closes drizzle-team#5789
@momomuchu momomuchu force-pushed the fix/postgres-js-date-serializer-5789 branch from 8eae44f to ef7c793 Compare July 5, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

postgres-js driver: transparent serializer override breaks JS Date params in raw sql`` templates

1 participant