From 166d19bcbd8e0a4392619c6e0130c66dd4250e26 Mon Sep 17 00:00:00 2001 From: jkomyno <12381818+jkomyno@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:32:36 +0200 Subject: [PATCH 1/5] feat(authoring): ship target-owned field presets for TS terseness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move scalar field presets to the owning layer so the TS callback surface and the PSL scalar surface lower to byte-identical contracts. - target-postgres now ships presets (text, int, bigint, float, decimal, boolean, json, bytes, dateTime, createdAt) using the same pg/* codec IDs and native types that the postgres adapter already emits for PSL scalars. This unblocks the terse field.int() / field.boolean() / field.json() / field.float() vocabulary the April milestone flagged as missing. - family-sql drops text, timestamp, and createdAt — their sql/* codec IDs never aligned with any real target's PSL mapping and blocked parity. The generator-aligned presets (uuid, ulid, nanoid, cuid2, ksuid, id.*) stay: their sql/char@1 codec is fixed by the ID generator metadata override in both PSL and TS paths. --- .../src/core/authoring-field-presets.ts | 41 ++++----- .../3-targets/postgres/src/core/authoring.ts | 90 ++++++++++++++++++- .../postgres/src/core/descriptor-meta.ts | 3 +- 3 files changed, 107 insertions(+), 27 deletions(-) diff --git a/packages/2-sql/9-family/src/core/authoring-field-presets.ts b/packages/2-sql/9-family/src/core/authoring-field-presets.ts index d3054be768..53af1a0799 100644 --- a/packages/2-sql/9-family/src/core/authoring-field-presets.ts +++ b/packages/2-sql/9-family/src/core/authoring-field-presets.ts @@ -1,5 +1,21 @@ import type { AuthoringFieldNamespace } from '@prisma-next/framework-components/authoring'; +/** + * Family-level SQL authoring field presets. + * + * Only presets whose codec IDs align with the ID generator metadata live here + * (see `@prisma-next/ids`). These presets are target-agnostic because the + * generator metadata fixes their codec/native-type to `sql/char@1` + * (`character`) regardless of target, and the PSL interpreter lets the + * generator override the scalar descriptor. + * + * Scalar presets that map to target-specific codecs (e.g. `text`, `int`, + * `boolean`, `dateTime`) are contributed by the target pack (see + * `postgresAuthoringFieldPresets` in `@prisma-next/target-postgres`) so the + * TS callback surface and the PSL scalar surface lower to byte-identical + * contracts for the active target. + */ + const CHARACTER_CODEC_ID = 'sql/char@1'; const CHARACTER_NATIVE_TYPE = 'character'; @@ -18,31 +34,6 @@ const nanoidOptionsArgument = { } as const; export const sqlFamilyAuthoringFieldPresets = { - text: { - kind: 'fieldPreset', - output: { - codecId: 'sql/text@1', - nativeType: 'text', - }, - }, - timestamp: { - kind: 'fieldPreset', - output: { - codecId: 'sql/timestamp@1', - nativeType: 'timestamp', - }, - }, - createdAt: { - kind: 'fieldPreset', - output: { - codecId: 'sql/timestamp@1', - nativeType: 'timestamp', - default: { - kind: 'function', - expression: 'CURRENT_TIMESTAMP', - }, - }, - }, uuid: { kind: 'fieldPreset', output: { diff --git a/packages/3-targets/3-targets/postgres/src/core/authoring.ts b/packages/3-targets/3-targets/postgres/src/core/authoring.ts index 5474c60032..3046cb9f80 100644 --- a/packages/3-targets/3-targets/postgres/src/core/authoring.ts +++ b/packages/3-targets/3-targets/postgres/src/core/authoring.ts @@ -1,4 +1,7 @@ -import type { AuthoringTypeNamespace } from '@prisma-next/framework-components/authoring'; +import type { + AuthoringFieldNamespace, + AuthoringTypeNamespace, +} from '@prisma-next/framework-components/authoring'; export const postgresAuthoringTypes = { enum: { @@ -13,3 +16,88 @@ export const postgresAuthoringTypes = { }, }, } as const satisfies AuthoringTypeNamespace; + +/** + * Field presets contributed by the Postgres target pack. + * + * These mirror the PSL scalar-to-codec mapping used by the Postgres adapter + * (see `createPostgresPslScalarTypeDescriptors`), so that authoring a field + * via the TS callback surface (e.g. `field.int()`) and via the PSL scalar + * surface (e.g. `Int`) lowers to byte-identical contracts. + */ +export const postgresAuthoringFieldPresets = { + text: { + kind: 'fieldPreset', + output: { + codecId: 'pg/text@1', + nativeType: 'text', + }, + }, + int: { + kind: 'fieldPreset', + output: { + codecId: 'pg/int4@1', + nativeType: 'int4', + }, + }, + bigint: { + kind: 'fieldPreset', + output: { + codecId: 'pg/int8@1', + nativeType: 'int8', + }, + }, + float: { + kind: 'fieldPreset', + output: { + codecId: 'pg/float8@1', + nativeType: 'float8', + }, + }, + decimal: { + kind: 'fieldPreset', + output: { + codecId: 'pg/numeric@1', + nativeType: 'numeric', + }, + }, + boolean: { + kind: 'fieldPreset', + output: { + codecId: 'pg/bool@1', + nativeType: 'bool', + }, + }, + json: { + kind: 'fieldPreset', + output: { + codecId: 'pg/jsonb@1', + nativeType: 'jsonb', + }, + }, + bytes: { + kind: 'fieldPreset', + output: { + codecId: 'pg/bytea@1', + nativeType: 'bytea', + }, + }, + dateTime: { + kind: 'fieldPreset', + output: { + codecId: 'pg/timestamptz@1', + nativeType: 'timestamptz', + }, + }, + createdAt: { + kind: 'fieldPreset', + output: { + codecId: 'pg/timestamptz@1', + nativeType: 'timestamptz', + default: { + kind: 'function', + expression: 'now()', + }, + }, + }, +} as const satisfies AuthoringFieldNamespace; diff --git a/packages/3-targets/3-targets/postgres/src/core/descriptor-meta.ts b/packages/3-targets/3-targets/postgres/src/core/descriptor-meta.ts index 5f15ac9906..b1079d0596 100644 --- a/packages/3-targets/3-targets/postgres/src/core/descriptor-meta.ts +++ b/packages/3-targets/3-targets/postgres/src/core/descriptor-meta.ts @@ -1,4 +1,4 @@ -import { postgresAuthoringTypes } from './authoring'; +import { postgresAuthoringFieldPresets, postgresAuthoringTypes } from './authoring'; export const postgresTargetDescriptorMeta = { kind: 'target', @@ -9,5 +9,6 @@ export const postgresTargetDescriptorMeta = { capabilities: {}, authoring: { type: postgresAuthoringTypes, + field: postgresAuthoringFieldPresets, }, } as const; From 239f97ed5b49e071306f1fb88dee61ca672550b5 Mon Sep 17 00:00:00 2001 From: jkomyno <12381818+jkomyno@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:32:50 +0200 Subject: [PATCH 2/5] test(integration): add callback-mode-scalars parity fixture Prove VP2 parity end-to-end: author the same contract in PSL and in the TS callback surface using the new target-postgres field presets, and verify both paths emit byte-identical contract.json, storageHash, profileHash, and executionHash. The fixture covers the common scalar vocabulary (int with autoincrement, text with unique, int, boolean with literal default, float optional, json optional, DateTime with now() default) plus a cascading belongsTo relation. It slots into the existing cli.emit-parity-fixtures runner, so no test-harness changes are needed. --- .../parity/callback-mode-scalars/contract.ts | 34 +++ .../expected.contract.json | 284 ++++++++++++++++++ .../parity/callback-mode-scalars/packs.ts | 1 + .../callback-mode-scalars/schema.prisma | 17 ++ 4 files changed, 336 insertions(+) create mode 100644 test/integration/test/authoring/parity/callback-mode-scalars/contract.ts create mode 100644 test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json create mode 100644 test/integration/test/authoring/parity/callback-mode-scalars/packs.ts create mode 100644 test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts b/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts new file mode 100644 index 0000000000..adef617f1f --- /dev/null +++ b/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts @@ -0,0 +1,34 @@ +import sqlFamily from '@prisma-next/family-sql/pack'; +import { defineContract, rel } from '@prisma-next/sql-contract-ts/contract-builder'; +import postgresPack from '@prisma-next/target-postgres/pack'; + +export const contract = defineContract( + { family: sqlFamily, target: postgresPack }, + ({ field, model }) => { + const User = model('User', { + fields: { + id: field.int().defaultSql('autoincrement()').id(), + email: field.text().unique(), + age: field.int(), + isActive: field.boolean().default(true), + score: field.float().optional(), + profile: field.json().optional(), + createdAt: field.createdAt(), + }, + }).sql({ table: 'user' }); + const Post = model('Post', { + fields: { + id: field.int().defaultSql('autoincrement()').id(), + userId: field.int(), + title: field.text(), + rating: field.float().optional(), + }, + relations: { + user: rel + .belongsTo(User, { from: 'userId', to: 'id' }) + .sql({ fk: { onDelete: 'cascade', onUpdate: 'cascade' } }), + }, + }).sql({ table: 'post' }); + return { models: { User, Post } }; + }, +); diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json new file mode 100644 index 0000000000..e5b8db0dd7 --- /dev/null +++ b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json @@ -0,0 +1,284 @@ +{ + "schemaVersion": "1", + "targetFamily": "sql", + "target": "postgres", + "profileHash": "sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e", + "roots": { + "post": "Post", + "user": "User" + }, + "models": { + "Post": { + "fields": { + "id": { + "nullable": false, + "type": { + "codecId": "pg/int4@1", + "kind": "scalar" + } + }, + "rating": { + "nullable": true, + "type": { + "codecId": "pg/float8@1", + "kind": "scalar" + } + }, + "title": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "userId": { + "nullable": false, + "type": { + "codecId": "pg/int4@1", + "kind": "scalar" + } + } + }, + "relations": { + "user": { + "cardinality": "N:1", + "on": { + "localFields": ["userId"], + "targetFields": ["id"] + }, + "to": "User" + } + }, + "storage": { + "fields": { + "id": { + "column": "id" + }, + "rating": { + "column": "rating" + }, + "title": { + "column": "title" + }, + "userId": { + "column": "userId" + } + }, + "table": "post" + } + }, + "User": { + "fields": { + "age": { + "nullable": false, + "type": { + "codecId": "pg/int4@1", + "kind": "scalar" + } + }, + "createdAt": { + "nullable": false, + "type": { + "codecId": "pg/timestamptz@1", + "kind": "scalar" + } + }, + "email": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "id": { + "nullable": false, + "type": { + "codecId": "pg/int4@1", + "kind": "scalar" + } + }, + "isActive": { + "nullable": false, + "type": { + "codecId": "pg/bool@1", + "kind": "scalar" + } + }, + "profile": { + "nullable": true, + "type": { + "codecId": "pg/jsonb@1", + "kind": "scalar" + } + }, + "score": { + "nullable": true, + "type": { + "codecId": "pg/float8@1", + "kind": "scalar" + } + } + }, + "relations": {}, + "storage": { + "fields": { + "age": { + "column": "age" + }, + "createdAt": { + "column": "createdAt" + }, + "email": { + "column": "email" + }, + "id": { + "column": "id" + }, + "isActive": { + "column": "isActive" + }, + "profile": { + "column": "profile" + }, + "score": { + "column": "score" + } + }, + "table": "user" + } + } + }, + "storage": { + "storageHash": "sha256:b5d91d64118c026ef978b084441b127f0b5a8f8c1a1243290c2738a5ce088811", + "tables": { + "post": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" + }, + "nativeType": "int4", + "nullable": false + }, + "rating": { + "codecId": "pg/float8@1", + "nativeType": "float8", + "nullable": true + }, + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [ + { + "columns": ["userId"], + "constraint": true, + "index": true, + "onDelete": "cascade", + "onUpdate": "cascade", + "references": { + "columns": ["id"], + "table": "user" + } + } + ], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] + }, + "user": { + "columns": { + "age": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + }, + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" + }, + "nativeType": "timestamptz", + "nullable": false + }, + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" + }, + "nativeType": "int4", + "nullable": false + }, + "isActive": { + "codecId": "pg/bool@1", + "default": { + "kind": "literal", + "value": true + }, + "nativeType": "bool", + "nullable": false + }, + "profile": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true + }, + "score": { + "codecId": "pg/float8@1", + "nativeType": "float8", + "nullable": true + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [ + { + "columns": ["email"] + } + ] + } + } + }, + "capabilities": { + "postgres": { + "jsonAgg": true, + "lateral": true, + "limit": true, + "orderBy": true, + "returning": true + }, + "sql": { + "defaultInInsert": true, + "enums": true, + "returning": true + } + }, + "extensionPacks": {}, + "meta": {}, + "_generated": { + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", + "message": "This file is automatically generated by \"prisma-next contract emit\".", + "regenerate": "To regenerate, run: prisma-next contract emit" + } +} diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/packs.ts b/test/integration/test/authoring/parity/callback-mode-scalars/packs.ts new file mode 100644 index 0000000000..50c82b5740 --- /dev/null +++ b/test/integration/test/authoring/parity/callback-mode-scalars/packs.ts @@ -0,0 +1 @@ +export const extensionPacks = [] as const; diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma b/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma new file mode 100644 index 0000000000..8e890e6342 --- /dev/null +++ b/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma @@ -0,0 +1,17 @@ +model User { + id Int @id @default(autoincrement()) + email String @unique + age Int + isActive Boolean @default(true) + score Float? + profile Json? + createdAt DateTime @default(now()) +} + +model Post { + id Int @id @default(autoincrement()) + userId Int + title String + rating Float? + user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) +} From f09141a45a4893c0f53f648fc6b80282086221a1 Mon Sep 17 00:00:00 2001 From: jkomyno <12381818+jkomyno@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:33:03 +0200 Subject: [PATCH 3/5] test(integration): assert callback-mode terseness stays under 2.1x PSL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the formal terseness comparison the April milestone's VP2 task 2 asked for: count semantic lines (non-blank, non-comment) in the callback-mode-scalars fixture's schema.prisma and contract.ts, and assert the ratio stays under 2.1x. Also assert the ratio is strictly tighter than the structural core-surface baseline so regressions in the preset vocabulary (or in the composed helper pipeline) can't silently widen the window. Current numbers: callback-mode-scalars 16→33 = 2.06x, core-surface 24→59 = 2.46x — measurably under the 3–5x baseline the milestone doc called out. --- .../authoring/callback-mode-terseness.test.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/integration/test/authoring/callback-mode-terseness.test.ts diff --git a/test/integration/test/authoring/callback-mode-terseness.test.ts b/test/integration/test/authoring/callback-mode-terseness.test.ts new file mode 100644 index 0000000000..7ec7807e4e --- /dev/null +++ b/test/integration/test/authoring/callback-mode-terseness.test.ts @@ -0,0 +1,52 @@ +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const fixtureDir = join(__dirname, 'parity', 'callback-mode-scalars'); + +/** + * Counts semantic lines in a source file — non-blank lines that are not + * comments. Matches the heuristic used by the contract-psl ts-psl-parity + * test so results are comparable across parity tests. + */ +function countSemanticLines(source: string): number { + return source + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0 && !line.startsWith('//')).length; +} + +describe('VP2: TS callback-mode authoring terseness parity', () => { + const pslSource = readFileSync(join(fixtureDir, 'schema.prisma'), 'utf-8'); + const tsSource = readFileSync(join(fixtureDir, 'contract.ts'), 'utf-8'); + const pslLines = countSemanticLines(pslSource); + const tsLines = countSemanticLines(tsSource); + const ratio = tsLines / pslLines; + + it('keeps the callback-mode TS contract in the ~1.5–2.1x PSL ballpark', () => { + // VP2 stop condition: "The TypeScript version of a representative + // contract is in the same ballpark of length as the PSL version." + // + // Baseline (April milestone): structural TS authoring was ~3–5x the + // PSL version. The callback-mode field presets (contributed by + // @prisma-next/target-postgres/pack) should collapse scalar fields to + // one line each, pulling the ratio well under the baseline. + // + // The upper bound of 2.1x is intentional: any drift above 2.1x should + // force a re-review of the preset vocabulary rather than silently + // widen the acceptance window. + expect(ratio).toBeLessThanOrEqual(2.1); + expect(ratio).toBeGreaterThan(0); + }); + + it('is measurably tighter than the structural core-surface baseline', () => { + const coreSurfaceDir = join(__dirname, 'parity', 'core-surface'); + const coreSurfacePsl = readFileSync(join(coreSurfaceDir, 'schema.prisma'), 'utf-8'); + const coreSurfaceTs = readFileSync(join(coreSurfaceDir, 'contract.ts'), 'utf-8'); + const coreRatio = countSemanticLines(coreSurfaceTs) / countSemanticLines(coreSurfacePsl); + + expect(ratio).toBeLessThan(coreRatio); + }); +}); From 7448e0857929f0748e9222e015ced5dbe38de7b0 Mon Sep 17 00:00:00 2001 From: jkomyno <12381818+jkomyno@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:24:53 +0200 Subject: [PATCH 4/5] test(integration): extend callback-mode-scalars with pgvector extension type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the last gap in VP2's user story — "a representative contract (multiple models, relations, at least one extension type)". The fixture now declares an `Embedding` named type via `@pgvector.column(length: 1536)` in PSL and via the `type.pgvector.vector(n)` callback-mode helper contributed by the real `@prisma-next/extension-pgvector/pack`, and uses it through `field.namedType(types.Embedding).optional()` on the `User` model. Terseness actually improves: 20 PSL → 38 TS = 1.90x (was 16 → 33 = 2.06x), because a reusable named type amortizes better in TS than the 3-line `types { ... }` block in PSL. Still well under the 2.1x test bound in callback-mode-terseness.test.ts, with meaningful breathing room for future drift. No changes to any package source — the fix is entirely in the fixture. --- .../parity/callback-mode-scalars/contract.ts | 11 ++- .../expected.contract.json | 81 ++++++++++++++++++- .../parity/callback-mode-scalars/packs.ts | 4 +- .../callback-mode-scalars/schema.prisma | 5 ++ 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts b/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts index adef617f1f..566c53dfa8 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts +++ b/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts @@ -1,10 +1,14 @@ +import pgvector from '@prisma-next/extension-pgvector/pack'; import sqlFamily from '@prisma-next/family-sql/pack'; import { defineContract, rel } from '@prisma-next/sql-contract-ts/contract-builder'; import postgresPack from '@prisma-next/target-postgres/pack'; export const contract = defineContract( - { family: sqlFamily, target: postgresPack }, - ({ field, model }) => { + { family: sqlFamily, target: postgresPack, extensionPacks: { pgvector } }, + ({ field, model, type }) => { + const types = { + Embedding: type.pgvector.vector(1536), + } as const; const User = model('User', { fields: { id: field.int().defaultSql('autoincrement()').id(), @@ -13,6 +17,7 @@ export const contract = defineContract( isActive: field.boolean().default(true), score: field.float().optional(), profile: field.json().optional(), + embedding: field.namedType(types.Embedding).optional(), createdAt: field.createdAt(), }, }).sql({ table: 'user' }); @@ -29,6 +34,6 @@ export const contract = defineContract( .sql({ fk: { onDelete: 'cascade', onUpdate: 'cascade' } }), }, }).sql({ table: 'post' }); - return { models: { User, Post } }; + return { types, models: { User, Post } }; }, ); diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json index e5b8db0dd7..d13db608b5 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json +++ b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json @@ -90,6 +90,13 @@ "kind": "scalar" } }, + "embedding": { + "nullable": true, + "type": { + "codecId": "pg/vector@1", + "kind": "scalar" + } + }, "id": { "nullable": false, "type": { @@ -131,6 +138,9 @@ "email": { "column": "email" }, + "embedding": { + "column": "embedding" + }, "id": { "column": "id" }, @@ -149,7 +159,7 @@ } }, "storage": { - "storageHash": "sha256:b5d91d64118c026ef978b084441b127f0b5a8f8c1a1243290c2738a5ce088811", + "storageHash": "sha256:615d7f6e37b15d66c64b8e500ee79877eecae9bb484d48b429f3b3c1d1e21051", "tables": { "post": { "columns": { @@ -218,6 +228,12 @@ "nativeType": "text", "nullable": false }, + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": true, + "typeRef": "Embedding" + }, "id": { "codecId": "pg/int4@1", "default": { @@ -258,6 +274,15 @@ } ] } + }, + "types": { + "Embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "typeParams": { + "length": 1536 + } + } } }, "capabilities": { @@ -266,6 +291,7 @@ "lateral": true, "limit": true, "orderBy": true, + "pgvector/cosine": true, "returning": true }, "sql": { @@ -274,7 +300,58 @@ "returning": true } }, - "extensionPacks": {}, + "extensionPacks": { + "pgvector": { + "capabilities": { + "postgres": { + "pgvector/cosine": true + } + }, + "familyId": "sql", + "id": "pgvector", + "kind": "extension", + "targetId": "postgres", + "types": { + "codecTypes": { + "import": { + "alias": "PgVectorTypes", + "named": "CodecTypes", + "package": "@prisma-next/extension-pgvector/codec-types" + }, + "typeImports": [ + { + "alias": "Vector", + "named": "Vector", + "package": "@prisma-next/extension-pgvector/codec-types" + } + ] + }, + "operationTypes": { + "import": { + "alias": "PgVectorOperationTypes", + "named": "OperationTypes", + "package": "@prisma-next/extension-pgvector/operation-types" + } + }, + "queryOperationTypes": { + "import": { + "alias": "PgVectorQueryOperationTypes", + "named": "QueryOperationTypes", + "package": "@prisma-next/extension-pgvector/operation-types" + } + }, + "storage": [ + { + "familyId": "sql", + "nativeType": "vector", + "targetId": "postgres", + "typeId": "pg/vector@1" + } + ] + }, + "version": "0.0.1" + } + }, "meta": {}, "_generated": { "warning": "⚠️ GENERATED FILE - DO NOT EDIT", diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/packs.ts b/test/integration/test/authoring/parity/callback-mode-scalars/packs.ts index 50c82b5740..03bd7070f6 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/packs.ts +++ b/test/integration/test/authoring/parity/callback-mode-scalars/packs.ts @@ -1 +1,3 @@ -export const extensionPacks = [] as const; +import pgvector from '@prisma-next/extension-pgvector/control'; + +export const extensionPacks = [pgvector] as const; diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma b/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma index 8e890e6342..aa316ae362 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma +++ b/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma @@ -1,3 +1,7 @@ +types { + Embedding = Bytes @pgvector.column(length: 1536) +} + model User { id Int @id @default(autoincrement()) email String @unique @@ -5,6 +9,7 @@ model User { isActive Boolean @default(true) score Float? profile Json? + embedding Embedding? createdAt DateTime @default(now()) } From 1417eb9e7489379d437e90326da89d84af411154 Mon Sep 17 00:00:00 2001 From: jkomyno <12381818+jkomyno@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:03:37 +0200 Subject: [PATCH 5/5] fix authoring callback-mode parity --- .../2-mongo-family/9-family/src/exports/pack.ts | 4 +--- .../3-extensions/pgvector/src/exports/pack.ts | 8 +++----- .../1-mongo-target/src/exports/pack.ts | 8 +++----- .../3-targets/postgres/src/exports/pack.ts | 8 +++----- .../3-targets/sqlite/src/exports/pack.ts | 3 +-- .../parity/callback-mode-scalars/contract.ts | 2 +- .../expected.contract.json | 4 ++-- .../parity/callback-mode-scalars/schema.prisma | 2 +- .../test/contract-builder.types.test-d.ts | 17 ++++++++++++++++- 9 files changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/2-mongo-family/9-family/src/exports/pack.ts b/packages/2-mongo-family/9-family/src/exports/pack.ts index e7e07c5d73..1e249b1797 100644 --- a/packages/2-mongo-family/9-family/src/exports/pack.ts +++ b/packages/2-mongo-family/9-family/src/exports/pack.ts @@ -1,5 +1,3 @@ -import type { FamilyPackRef } from '@prisma-next/framework-components/components'; - const mongoFamilyPack = { kind: 'family', id: 'mongo', @@ -7,4 +5,4 @@ const mongoFamilyPack = { version: '0.0.1', } as const; -export default mongoFamilyPack as typeof mongoFamilyPack & FamilyPackRef<'mongo'>; +export default mongoFamilyPack; diff --git a/packages/3-extensions/pgvector/src/exports/pack.ts b/packages/3-extensions/pgvector/src/exports/pack.ts index 8208e5a8fd..f7f921b06f 100644 --- a/packages/3-extensions/pgvector/src/exports/pack.ts +++ b/packages/3-extensions/pgvector/src/exports/pack.ts @@ -1,10 +1,8 @@ -import type { ExtensionPackRef } from '@prisma-next/framework-components/components'; import { pgvectorPackMeta } from '../core/descriptor-meta'; import type { CodecTypes } from '../types/codec-types'; const pgvectorPack = pgvectorPackMeta; -export default pgvectorPack as typeof pgvectorPackMeta & - ExtensionPackRef<'sql', 'postgres'> & { - readonly __codecTypes?: CodecTypes; - }; +export default pgvectorPack as typeof pgvectorPackMeta & { + readonly __codecTypes?: CodecTypes; +}; diff --git a/packages/3-mongo-target/1-mongo-target/src/exports/pack.ts b/packages/3-mongo-target/1-mongo-target/src/exports/pack.ts index ed895b7e8c..811e9442b2 100644 --- a/packages/3-mongo-target/1-mongo-target/src/exports/pack.ts +++ b/packages/3-mongo-target/1-mongo-target/src/exports/pack.ts @@ -1,10 +1,8 @@ -import type { TargetPackRef } from '@prisma-next/framework-components/components'; import { mongoTargetDescriptorMeta } from '../core/descriptor-meta'; import type { CodecTypes } from './codec-types'; const mongoTargetPack = mongoTargetDescriptorMeta; -export default mongoTargetPack as typeof mongoTargetPack & - TargetPackRef<'mongo', 'mongo'> & { - readonly __codecTypes?: CodecTypes; - }; +export default mongoTargetPack as typeof mongoTargetPack & { + readonly __codecTypes?: CodecTypes; +}; diff --git a/packages/3-targets/3-targets/postgres/src/exports/pack.ts b/packages/3-targets/3-targets/postgres/src/exports/pack.ts index 6c68c7f72c..a8f540bb0a 100644 --- a/packages/3-targets/3-targets/postgres/src/exports/pack.ts +++ b/packages/3-targets/3-targets/postgres/src/exports/pack.ts @@ -1,10 +1,8 @@ import type { CodecTypes } from '@prisma-next/adapter-postgres/codec-types'; -import type { TargetPackRef } from '@prisma-next/framework-components/components'; import { postgresTargetDescriptorMeta } from '../core/descriptor-meta'; const postgresPack = postgresTargetDescriptorMeta; -export default postgresPack as typeof postgresTargetDescriptorMeta & - TargetPackRef<'sql', 'postgres'> & { - readonly __codecTypes?: CodecTypes; - }; +export default postgresPack as typeof postgresTargetDescriptorMeta & { + readonly __codecTypes?: CodecTypes; +}; diff --git a/packages/3-targets/3-targets/sqlite/src/exports/pack.ts b/packages/3-targets/3-targets/sqlite/src/exports/pack.ts index de4f9afb48..e8d1e89844 100644 --- a/packages/3-targets/3-targets/sqlite/src/exports/pack.ts +++ b/packages/3-targets/3-targets/sqlite/src/exports/pack.ts @@ -1,9 +1,8 @@ import type { CodecTypes } from '@prisma-next/adapter-sqlite/codec-types'; -import type { TargetPackRef } from '@prisma-next/framework-components/components'; import { sqliteTargetDescriptorMeta } from '../core/descriptor-meta'; const sqlitePack = sqliteTargetDescriptorMeta; -export default sqlitePack as TargetPackRef<'sql', 'sqlite'> & { +export default sqlitePack as typeof sqliteTargetDescriptorMeta & { readonly __codecTypes?: CodecTypes; }; diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts b/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts index 566c53dfa8..c8f6f6b99c 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts +++ b/test/integration/test/authoring/parity/callback-mode-scalars/contract.ts @@ -7,7 +7,7 @@ export const contract = defineContract( { family: sqlFamily, target: postgresPack, extensionPacks: { pgvector } }, ({ field, model, type }) => { const types = { - Embedding: type.pgvector.vector(1536), + Embedding: type.pgvector.Vector(1536), } as const; const User = model('User', { fields: { diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json index d13db608b5..3113ab7fb0 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json +++ b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json @@ -291,7 +291,7 @@ "lateral": true, "limit": true, "orderBy": true, - "pgvector/cosine": true, + "pgvector.cosine": true, "returning": true }, "sql": { @@ -304,7 +304,7 @@ "pgvector": { "capabilities": { "postgres": { - "pgvector/cosine": true + "pgvector.cosine": true } }, "familyId": "sql", diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma b/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma index aa316ae362..62dde07ca0 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma +++ b/test/integration/test/authoring/parity/callback-mode-scalars/schema.prisma @@ -1,5 +1,5 @@ types { - Embedding = Bytes @pgvector.column(length: 1536) + Embedding = pgvector.Vector(1536) } model User { diff --git a/test/integration/test/contract-builder.types.test-d.ts b/test/integration/test/contract-builder.types.test-d.ts index aa30730907..6e8d3eb93c 100644 --- a/test/integration/test/contract-builder.types.test-d.ts +++ b/test/integration/test/contract-builder.types.test-d.ts @@ -187,9 +187,15 @@ test('integrated callback authoring exposes composition-shaped type helpers', () models: { User: model('User', { fields: { - id: field.id.uuidv7(), + id: field.int().defaultSql('autoincrement()').id(), + email: field.text().unique(), + age: field.int(), + isActive: field.boolean().default(true), + score: field.float().optional(), + profile: field.json().optional(), role: field.namedType(Role), embedding: field.namedType(Embedding).optional(), + createdAt: field.createdAt(), }, }).sql({ table: 'user', @@ -204,6 +210,15 @@ test('integrated callback authoring exposes composition-shaped type helpers', () expectTypeOf().toEqualTypeOf<'Role' | 'Embedding'>(); expectTypeOf().toEqualTypeOf<'pg/enum@1'>(); expectTypeOf().toEqualTypeOf<'pg/vector@1'>(); + expectTypeOf(contract.storage.tables.user.columns.id.codecId).toEqualTypeOf<'pg/int4@1'>(); + expectTypeOf(contract.storage.tables.user.columns.email.codecId).toEqualTypeOf<'pg/text@1'>(); + expectTypeOf(contract.storage.tables.user.columns.age.codecId).toEqualTypeOf<'pg/int4@1'>(); + expectTypeOf(contract.storage.tables.user.columns.isActive.codecId).toEqualTypeOf<'pg/bool@1'>(); + expectTypeOf(contract.storage.tables.user.columns.score.codecId).toEqualTypeOf<'pg/float8@1'>(); + expectTypeOf(contract.storage.tables.user.columns.profile.codecId).toEqualTypeOf<'pg/jsonb@1'>(); + expectTypeOf( + contract.storage.tables.user.columns.createdAt.codecId, + ).toEqualTypeOf<'pg/timestamptz@1'>(); expectTypeOf(contract.storage.tables.user.columns.role.typeRef).toEqualTypeOf<'Role'>(); expectTypeOf(contract.storage.tables.user.columns.embedding.typeRef).toEqualTypeOf<'Embedding'>(); });