From e9917d69df40fe262a494f5a6d7d96caa1372f4a Mon Sep 17 00:00:00 2001 From: tahiraltundag Date: Tue, 15 Sep 2026 17:00:31 +0300 Subject: [PATCH 1/3] fix(postgres): infer timestamptz as TimestamptzString to avoid Temporal runtime error Fixes #30252: contract infer emitted Timestamptz which requires Node Temporal API, failing on Node 24 with RUNTIME.TEMPORAL_UNAVAILABLE. Scaffold already uses TimestamptzString, so infer now aligns to String variant, preventing brownfield mismatch and CONTRACT.MARKER_MISMATCH on Timestamptz vs TimestamptzString switch. - postgres-type-map: map timestamptz / timestamp with time zone to TimestamptzString (including parameterized) - tests: update expectations and snapshots Signed-off-by: tahiraltundag Signed-off-by: tahiraltundag --- .../src/core/psl-infer/postgres-type-map.ts | 6 +++--- .../test/psl-infer/infer-parse-emit.test.ts | 8 ++++---- .../test/psl-infer/postgres-type-map.test.ts | 15 ++++++--------- .../psl-infer/print-psl/print-psl.core.test.ts | 2 +- .../print-psl.defaults-and-types.test.ts | 18 +++++++++--------- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts b/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts index 769786748f4e..cfe22fc1bd02 100644 --- a/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts +++ b/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts @@ -29,8 +29,8 @@ const PRESERVED_NATIVE_TYPES: Record = { decimal: 'Numeric', timestamp: 'Timestamp', 'timestamp without time zone': 'Timestamp', - timestamptz: 'Timestamptz', - 'timestamp with time zone': 'Timestamptz', + timestamptz: 'TimestamptzString', + 'timestamp with time zone': 'TimestamptzString', date: 'Date', time: 'Time', 'time without time zone': 'Time', @@ -47,7 +47,7 @@ const PARAMETERIZED_NATIVE_TYPES: Record = { numeric: 'Numeric', decimal: 'Numeric', timestamp: 'Timestamp', - timestamptz: 'Timestamptz', + timestamptz: 'TimestamptzString', time: 'Time', timetz: 'Timetz', }; diff --git a/packages/3-targets/3-targets/postgres/test/psl-infer/infer-parse-emit.test.ts b/packages/3-targets/3-targets/postgres/test/psl-infer/infer-parse-emit.test.ts index cde3ec95f971..6942730b3c63 100644 --- a/packages/3-targets/3-targets/postgres/test/psl-infer/infer-parse-emit.test.ts +++ b/packages/3-targets/3-targets/postgres/test/psl-infer/infer-parse-emit.test.ts @@ -21,9 +21,9 @@ const authoringTypes = { Int: { kind: 'typeConstructor', output: { codecId: 'pg/int4@1', nativeType: 'int4' } }, Uuid: { kind: 'typeConstructor', output: { codecId: 'pg/uuid@1', nativeType: 'uuid' } }, Inet: { kind: 'typeConstructor', output: { codecId: 'pg/inet@1', nativeType: 'inet' } }, - Timestamptz: { + TimestamptzString: { kind: 'typeConstructor', - output: { codecId: 'pg/timestamptz-temporal@1', nativeType: 'timestamptz' }, + output: { codecId: 'pg/timestamptz-string@1', nativeType: 'timestamptz' }, }, VarChar: { kind: 'typeConstructor', @@ -136,7 +136,7 @@ describe('Postgres PSL inference round trip', () => { expect(inferred).not.toContain('bareAmount Numeric()'); expect(inferred).toMatch(/jsonValue\s+Json/); expect(inferred).toMatch(/jsonbValue\s+Jsonb/); - expect(inferred).toMatch(/occurredAt\s+Timestamptz/); + expect(inferred).toMatch(/occurredAt\s+TimestamptzString/); expect(inferred).toMatch(/label\s+VarChar\(191\)/); const emitted = parseAndEmit(inferred); @@ -164,7 +164,7 @@ describe('Postgres PSL inference round trip', () => { json_value: { codecId: 'pg/json@1', nativeType: 'json', nullable: false }, jsonb_value: { codecId: 'pg/jsonb@1', nativeType: 'jsonb', nullable: false }, occurred_at: { - codecId: 'pg/timestamptz-temporal@1', + codecId: 'pg/timestamptz-string@1', nativeType: 'timestamptz', nullable: false, }, diff --git a/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts b/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts index a77d62b9cd5f..8ecdb7220d8a 100644 --- a/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts +++ b/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts @@ -14,11 +14,11 @@ describe('createPostgresTypeMap', () => { nativeType: 'numeric', }); expect(typeMap.resolve('timestamptz')).toEqual({ - pslType: { name: 'Timestamptz' }, + pslType: { name: 'TimestamptzString' }, nativeType: 'timestamptz', }); expect(typeMap.resolve('timestamp with time zone')).toEqual({ - pslType: { name: 'Timestamptz' }, + pslType: { name: 'TimestamptzString' }, nativeType: 'timestamp with time zone', }); expect(typeMap.resolve('jsonb')).toEqual({ pslType: { name: 'Jsonb' }, nativeType: 'jsonb' }); @@ -145,8 +145,8 @@ describe('representation-explicit spellings stay out of introspection', () => { ['date', 'Date'], ['timestamp', 'Timestamp'], ['timestamp without time zone', 'Timestamp'], - ['timestamptz', 'Timestamptz'], - ['timestamp with time zone', 'Timestamptz'], + ['timestamptz', 'TimestamptzString'], + ['timestamp with time zone', 'TimestamptzString'], ['time', 'Time'], ['time without time zone', 'Time'], ])('resolves %s to the bare %s, never a *String spelling', (nativeType, pslName) => { @@ -155,21 +155,18 @@ describe('representation-explicit spellings stay out of introspection', () => { it('keeps precision on the bare spelling', () => { expect(map.resolve('timestamptz(6)')).toMatchObject({ - pslType: { name: 'Timestamptz', args: ['6'] }, + pslType: { name: 'TimestamptzString', args: ['6'] }, }); }); - it('never produces a *String name for any native type it knows', () => { + it('never produces a *String name for any native type it knows except timestamptz', () => { const natives = [ 'date', 'timestamp', 'timestamp without time zone', - 'timestamptz', - 'timestamp with time zone', 'time', 'time without time zone', 'timetz', - 'timestamptz(6)', 'timestamp(3)', 'time(3)', ]; diff --git a/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.core.test.ts b/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.core.test.ts index fae0d6cd5736..c8b729084f70 100644 --- a/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.core.test.ts +++ b/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.core.test.ts @@ -75,7 +75,7 @@ describe('printPsl', () => { // WARNING: This table has no primary key in the database model AuditLog { event String - timestamp Timestamptz + timestamp TimestamptzString @@map("audit_log") } diff --git a/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.defaults-and-types.test.ts b/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.defaults-and-types.test.ts index a31ef40dfe96..653a69096117 100644 --- a/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.defaults-and-types.test.ts +++ b/packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.defaults-and-types.test.ts @@ -53,11 +53,11 @@ describe('printPsl', () => { // Contract inferred from the live database schema. Edit as needed, then run \`prisma contract emit\`. model Post { - id Int @id @default(autoincrement()) - title String @default("Untitled") - isPublished Boolean @default(false) @map("is_published") - viewCount Int @default(0) @map("view_count") - createdAt Timestamptz @default(now()) @map("created_at") + id Int @id @default(autoincrement()) + title String @default("Untitled") + isPublished Boolean @default(false) @map("is_published") + viewCount Int @default(0) @map("view_count") + createdAt TimestamptzString @default(now()) @map("created_at") @@map("post") } @@ -431,10 +431,10 @@ describe('printPsl', () => { // Contract inferred from the live database schema. Edit as needed, then run \`prisma contract emit\`. model Data { - id Int @id - computed String @default(dbgenerated("my_custom_func()")) - payload Jsonb @default(dbgenerated("'{}'::jsonb")) - touchedAt Timestamptz @default(dbgenerated("clock_timestamp()")) @map("touched_at") + id Int @id + computed String @default(dbgenerated("my_custom_func()")) + payload Jsonb @default(dbgenerated("'{}'::jsonb")) + touchedAt TimestamptzString @default(dbgenerated("clock_timestamp()")) @map("touched_at") @@map("data") } From 5ff54dfcc95bb9ccda9ac601756cad0afddeff42 Mon Sep 17 00:00:00 2001 From: tahiraltundag Date: Tue, 15 Sep 2026 17:09:28 +0300 Subject: [PATCH 2/3] test(postgres): clarify timestamptz String test descriptions Coderabbit nitpick: update parameterized mapping test titles to match TimestamptzString rule. Positive case title now notes timestamptz variants use TimestamptzString instead of never a *String. Negative case title now lists all excluded inputs (timestamptz, timestamp with time zone, timestamptz(6)). Signed-off-by: tahiraltundag --- .../postgres/test/psl-infer/postgres-type-map.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts b/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts index 8ecdb7220d8a..08e3828a418e 100644 --- a/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts +++ b/packages/3-targets/3-targets/postgres/test/psl-infer/postgres-type-map.test.ts @@ -149,7 +149,7 @@ describe('representation-explicit spellings stay out of introspection', () => { ['timestamp with time zone', 'TimestamptzString'], ['time', 'Time'], ['time without time zone', 'Time'], - ])('resolves %s to the bare %s, never a *String spelling', (nativeType, pslName) => { + ])('resolves %s to %s (timestamptz variants use TimestamptzString)', (nativeType, pslName) => { expect(map.resolve(nativeType)).toMatchObject({ pslType: { name: pslName } }); }); @@ -159,7 +159,7 @@ describe('representation-explicit spellings stay out of introspection', () => { }); }); - it('never produces a *String name for any native type it knows except timestamptz', () => { + it('never produces a *String name for any native type it knows except timestamptz variants (timestamptz, timestamp with time zone, timestamptz(6))', () => { const natives = [ 'date', 'timestamp', From be29797f5778abea2bcc63e9f3746cb2aa327a4e Mon Sep 17 00:00:00 2001 From: tahiraltundag Date: Tue, 15 Sep 2026 17:18:32 +0300 Subject: [PATCH 3/3] fix(postgres): map parameterized timestamp with time zone to TimestamptzString Coderabbit major: inferPostgresPslContract resolves via createPostgresTypeMap. PARAMETERIZED_NATIVE_TYPES lacked the long spelling, so timestamp with time zone(6) fell through to Unsupported instead of TimestamptzString(6) with precision. Add the missing entry so the parameterized path preserves args via existing splitTypeParameterList/buildScalarField flow. Signed-off-by: tahiraltundag --- .../3-targets/postgres/src/core/psl-infer/postgres-type-map.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts b/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts index cfe22fc1bd02..02efef15f2c9 100644 --- a/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts +++ b/packages/3-targets/3-targets/postgres/src/core/psl-infer/postgres-type-map.ts @@ -48,6 +48,7 @@ const PARAMETERIZED_NATIVE_TYPES: Record = { decimal: 'Numeric', timestamp: 'Timestamp', timestamptz: 'TimestamptzString', + 'timestamp with time zone': 'TimestamptzString', time: 'Time', timetz: 'Timetz', };