Skip to content

Commit e0e739c

Browse files
authored
TML-3061: PostgreSQL and SQLite codec descriptor protocols (#1051)
## Linked issue Refs [TML-3061](https://linear.app/prisma-company/issue/TML-3061/target-codec-descriptor-foundations) Base: `main`. Predecessor [#1023](#1023) is merged, so this PR has no stacked-base prerequisite. ## At a glance ```ts nativeTypeFor(ref: CodecRef): string { return this.nativeType(this.validateParams(ref)); } projectJson(expression: ProjectionExpr, ref: CodecRef): ProjectionExpr { const params = this.validateParams(ref); return ref.many === true ? this.jsonArrayProjection(expression, params) : this.jsonProjection(expression, params); } ``` A PostgreSQL descriptor now accepts the generic `CodecRef` that survives framework composition, validates its erased parameters, and enters target-owned typed behavior only after validation. ## Decision This PR ships the public PostgreSQL and SQLite codec descriptor protocols that the lossless JSON projection work will execute through: explicit generic-to-target adapters, narrow authoring helpers, structurally validated immutable registries, and coherent runtime/control adapter composition. It migrates every affected built-in and the pgvector, PostGIS, and arktype-json extensions to those protocols, moves PostgreSQL native-type lookup behind the validated descriptor boundary, and adds the `PublicCodecTypes` declaration-portability boundary plus extension-author migration guidance. ## Reviewer notes - The JSON projection hooks are public protocol capabilities, but production PostgreSQL and SQLite renderers deliberately remain pass-through. TML-3063 will activate descriptor dispatch and own the observable lossless JSON hard cut. - Codec JSON methods, emitted contracts, generated declarations, fixtures, codec IDs, and current SQL/JSON representations are unchanged. Drift in any of those surfaces was treated as a stop condition. - `PublicCodecTypes` intentionally exposes only each codec's `input`, `output`, and `traits` through composed contract declarations. This keeps downstream semantic typing exact without making exported declarations name concrete target descriptor implementation members. - PostgreSQL extension authors must adopt the target protocol and add the target package as a runtime dependency; direct adapter injection also moves to target-typed `codecDescriptors`. - Deterministic target, adapter, extension, declaration, workspace, documentation, skill, manifest, dependency, cast, throw, upgrade, fixture, and audit gates passed. The broad package, integration, and e2e suites were also run, but Postgres/resource-related flakes remained after varying and focused reruns, so their final broad confirmation is CI-deferred and is not claimed green here. ## How it fits together 1. [`PostgresCodecDescriptor`](packages/3-targets/3-targets/postgres/src/core/codec-descriptor.ts) and [`SqliteCodecDescriptor`](packages/3-targets/3-targets/sqlite/src/core/codec-descriptor.ts) add stable structural discriminants and public template methods. Each template method validates erased `CodecRef.typeParams` through the descriptor's Standard Schema before calling a strongly typed target hook. 2. `postgresCodec(...)`, `sqliteCodec(...)`, `definePostgresCodecs(...)`, and `defineSqliteCodecs(...)` make target ownership explicit while preserving generic SQL descriptor IDs, traits, parameter schemas, factories, renderers, metadata, and literal types. PostgreSQL supplies native-type plus scalar/array projection capabilities; SQLite supplies scalar projection and reports unsupported stored scalar-array refs. 3. Target, adapter, and ordered extension contributions are collected once per construction plane. The [PostgreSQL](packages/3-targets/6-adapters/postgres/src/core/codec-lookup.ts) and [SQLite](packages/3-targets/6-adapters/sqlite/src/core/codec-lookup.ts) builders structurally validate that set, reject malformed, wrong-target, raw, or duplicate descriptors, and derive ordinary materialization plus target lookup from the same immutable registry. 4. PostgreSQL parameter rendering now resolves trusted native type names through `nativeTypeFor(ref)` on the validated descriptor. Runtime, control, custom, enum, extension, and array cast spellings remain pinned to their existing SQL. 5. [`PublicCodecTypes`](packages/2-sql/2-authoring/contract-ts/src/contract-types.ts) projects extension codec maps to their public semantic fields before they enter contract types. Consumer declarations retain exact application types and traits while staying portable across package boundaries. 6. The [codec authoring guide](docs/reference/codec-authoring-guide.md) and the upgrade entry under [`skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/`](skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/) document target-owned authoring, coherent contribution sets, parameter-ref audits, and the dormant-hook transition. ## Behavior changes & evidence - **Extension authors can define and contribute target-owned PostgreSQL and SQLite codecs through public APIs.** The protocols validate erased refs before typed behavior, preserve concrete tuple/factory typing, and use structural validation across separately loaded packages. Implementation: [PostgreSQL descriptor protocol](packages/3-targets/3-targets/postgres/src/core/codec-descriptor.ts), [SQLite descriptor protocol](packages/3-targets/3-targets/sqlite/src/core/codec-descriptor.ts). Evidence: [PostgreSQL runtime tests](packages/3-targets/3-targets/postgres/test/postgres-codec-descriptor.test.ts), [SQLite runtime tests](packages/3-targets/3-targets/sqlite/test/sqlite-codec-descriptor.test.ts). - **PostgreSQL arrays have a target-owned default projection shape, while SQLite rejects undefined stored scalar-array semantics.** PostgreSQL's default lift binds the source once and preserves null arrays, empty arrays, null elements, and ordinality; SQLite raises a structured error for `CodecRef.many`. Implementation: [PostgreSQL descriptor protocol](packages/3-targets/3-targets/postgres/src/core/codec-descriptor.ts), [SQLite descriptor protocol](packages/3-targets/3-targets/sqlite/src/core/codec-descriptor.ts). Evidence: [PostgreSQL descriptor tests](packages/3-targets/3-targets/postgres/test/postgres-codec-descriptor.test.ts), [SQLite descriptor tests](packages/3-targets/3-targets/sqlite/test/sqlite-codec-descriptor.test.ts). - **Runtime, control, and direct adapter paths now see one coherent descriptor set and fail invalid composition early.** Bare adapters remain built-ins-only; stack composition includes ordered target, adapter, and extension contributions. Implementation: [PostgreSQL registry assembly](packages/3-targets/6-adapters/postgres/src/core/codec-lookup.ts), [SQLite registry assembly](packages/3-targets/6-adapters/sqlite/src/core/codec-lookup.ts). Evidence: [PostgreSQL composition tests](packages/3-targets/6-adapters/postgres/test/postgres-codec-registry-composition.test.ts), [SQLite composition tests](packages/3-targets/6-adapters/sqlite/test/sqlite-codec-registry-composition.test.ts). - **First-party PostgreSQL extensions use one canonical target-typed contribution set.** pgvector, PostGIS, and arktype-json expose the same descriptor arrays through runtime and control metadata while retaining current factories, application types, vector text, HEXEWKB, and structured JSON representations. Implementation: [pgvector codecs](packages/3-extensions/pgvector/src/core/codecs.ts), [PostGIS codecs](packages/3-extensions/postgis/src/core/codecs.ts), [arktype-json codec](packages/3-extensions/arktype-json/src/core/arktype-json-codec.ts). Evidence: [pgvector adoption tests](packages/3-extensions/pgvector/test/postgres-codec-descriptor-adoption.test.ts), [PostGIS adoption tests](packages/3-extensions/postgis/test/postgres-codec-descriptor-adoption.test.ts). - **Consumer-exported contracts remain declaration-portable.** Extension codec maps retain exact public input/output/trait semantics without exposing target descriptor implementation structure. Implementation: [`PublicCodecTypes`](packages/2-sql/2-authoring/contract-ts/src/contract-types.ts). Evidence: [declaration fixture](packages/3-extensions/pgvector/test/contract-declaration-portability.fixture.ts), [declaration type test](packages/3-extensions/pgvector/test/contract-declaration-portability.test-d.ts). - **Current rendering remains unchanged while the new hooks stay dormant.** PostgreSQL and SQLite still render codec, native, and document projections as their wrapped expressions; composition tests assert exact existing JSON SQL and zero descriptor projection calls. Implementation: [PostgreSQL JSON visitor](packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts), [SQLite JSON visitor](packages/3-targets/6-adapters/sqlite/src/core/adapter.ts). Evidence: [PostgreSQL composition tests](packages/3-targets/6-adapters/postgres/test/postgres-codec-registry-composition.test.ts), [SQLite composition tests](packages/3-targets/6-adapters/sqlite/test/sqlite-codec-registry-composition.test.ts). ## Compatibility / migration / risk - This is a pre-1.0 public SPI change for PostgreSQL-bound extension codecs. Extension descriptors must extend `PostgresCodecDescriptor` or use `postgresCodec(...)`, define target behavior, contribute a `definePostgresCodecs(...)` set through runtime and control paths, and declare `@prisma-next/target-postgres` as a production dependency. - PostgreSQL direct adapter options now accept target-typed `codecDescriptors` in place of an independently assembled generic `codecLookup`; SQLite gains the equivalent target-typed option. - Invalid dynamic composition now fails during adapter construction. Raw generic, wrong-target, structurally malformed, and duplicate descriptors no longer survive until lookup or query lowering. - `PublicCodecTypes` narrows incidental descriptor-derived members out of exported contract types while preserving `input`, `output`, and `traits`. Consumers depending on incidental implementation members through contract types must move to the descriptor API. - There is no data, emitted-contract, codec-ID, codec JSON, or fixture migration in this PR. Production JSON projection remains pass-through until TML-3063. ## Testing performed Passed on the final branch state: - Deterministic build, runtime test, typecheck, and lint gates for PostgreSQL/SQLite targets, PostgreSQL/SQLite adapters, pgvector, PostGIS, and arktype-json. - Root `pnpm build` and `pnpm typecheck`. - Runtime/type coverage for descriptor validation, target tuple preservation, adapter composition, native-type/cast parity, dormant JSON hooks, extension adoption, and `PublicCodecTypes` declaration portability. - Documentation, skill, and package-manifest validation; `pnpm lint:deps`; `pnpm lint:casts`; `pnpm lint:throws`; `pnpm check:upgrade-coverage --mode pr`; `pnpm fixtures:check`; and the bounded closing audits. Run, with final broad confirmation deferred to CI: - `pnpm test:packages` - `pnpm test:integration` - `pnpm test:e2e` Postgres/resource-related flakes remained after reruns at varying and focused scopes. These three broad suites are recorded as executed, not as passing. ## Skill update Updated the codec authoring documentation and the extension-author upgrade instructions in [`skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/`](skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/). The upgrade entry detects generic PostgreSQL extension descriptors and walks authors through runtime dependency placement, target subclassing or adaptation, canonical runtime/control contribution sets, required `CodecRef.typeParams`, and behavior-preservation checks. ## Follow-ups - TML-3063 will activate descriptor JSON projection dispatch, land the lossless PostgreSQL/SQLite and extension representations, and remove transitional generic metadata after production consumers have moved. ## Alternatives considered - **Keep target behavior in generic `CodecMeta` or add a framework-owned target map.** Rejected because target SQL/JSON behavior is open-world and belongs to each target package; generic framework composition remains target-neutral. - **Validate descriptors with `instanceof`.** Rejected because extensions may load a separate copy of a target package. Stable structural discriminants and required methods preserve interoperability across module identities. - **Maintain separate ordinary and target registries.** Rejected because the two views could disagree about membership, factories, or behavior. Both views are now derived from one validated descriptor set. - **Make identity JSON projection an implicit default.** Rejected because omission would silently assert that the database representation is already lossless. Every descriptor states the transitional behavior explicitly. - **Activate JSON projection hooks in this PR.** Rejected so registry/authoring migration and representation changes remain independently reviewable. TML-3063 owns the observable hard cut. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated for the public protocols, composition boundary, extensions, declaration portability, and unchanged rendering behavior. - [x] The PR title is in `TML-NNNN: <sentence-case title>` form and names the concrete deliverable. - [x] The **Skill update** section is filled in. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added PostgreSQL- and SQLite-specific codec descriptor authoring APIs, including descriptor/codec registries and validation. * Adapters now use provided target-specific codec descriptor sets to keep runtime and control-plane behavior consistent. * Improved native type handling and JSON projection behavior for parameterized and scalar-array scenarios. * **Bug Fixes** * PostGIS geometry codecs now support optional SRID, with updated typing, validation, and output rendering. * **Documentation** * Expanded codec authoring guide, target READMEs, and 0.16 → 0.17 upgrade instructions for adopting the new descriptor model. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
1 parent 4e51fef commit e0e739c

98 files changed

Lines changed: 5201 additions & 662 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/reference/codec-authoring-guide.md

Lines changed: 216 additions & 26 deletions
Large diffs are not rendered by default.

packages/2-sql/2-authoring/contract-ts/src/contract-types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@ import type { UnionToIntersection } from './authoring-type-utils';
2121
import type { AttributeStageIdFieldNames, FieldStateOf, ScalarFieldBuilder } from './contract-dsl';
2222
import type { EnumTypeHandle } from './enum-type';
2323

24+
export type PublicCodecTypes<CodecTypes extends Record<string, { readonly output: unknown }>> = {
25+
readonly [CodecId in keyof CodecTypes]: Pick<
26+
CodecTypes[CodecId],
27+
Extract<keyof CodecTypes[CodecId], 'input' | 'output' | 'traits'>
28+
>;
29+
};
30+
2431
export type ExtractCodecTypesFromPack<P> = P extends {
25-
__codecTypes?: infer C extends Record<string, { output: unknown }>;
32+
__codecTypes?: infer C extends Record<string, { readonly output: unknown }>;
2633
}
27-
? C
34+
? PublicCodecTypes<C>
2835
: Record<string, never>;
2936

3037
export type MergeExtensionCodecTypes<Packs extends Record<string, unknown>> = UnionToIntersection<

packages/3-extensions/arktype-json/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@prisma-next/framework-components": "workspace:0.16.0",
2222
"@prisma-next/sql-relational-core": "workspace:0.16.0",
2323
"@prisma-next/sql-runtime": "workspace:0.16.0",
24+
"@prisma-next/target-postgres": "workspace:0.16.0",
2425
"@standard-schema/spec": "^1.1.0",
2526
"arktype": "~2.2.2"
2627
},

packages/3-extensions/arktype-json/src/core/arktype-json-codec.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313

1414
import type { JsonValue } from '@prisma-next/contract/types';
1515
import {
16-
type AnyCodecDescriptor,
1716
type CodecCallContext,
18-
CodecDescriptorImpl,
1917
CodecImpl,
2018
type CodecInstanceContext,
2119
type ColumnHelperFor,
2220
type ColumnSpec,
2321
column,
2422
} from '@prisma-next/framework-components/codec';
2523
import { isRuntimeError, runtimeError } from '@prisma-next/framework-components/runtime';
24+
import type { ProjectionExpr } from '@prisma-next/sql-relational-core/ast';
25+
import {
26+
definePostgresCodecs,
27+
PostgresCodecDescriptor,
28+
} from '@prisma-next/target-postgres/codec-descriptor';
2629
import type { StandardSchemaV1 } from '@standard-schema/spec';
2730
import { ArkErrors, ark, type Type, type } from 'arktype';
2831

@@ -208,7 +211,13 @@ const arktypeJsonParamsSchema = type({
208211

209212
const ARKTYPE_JSON_META = { db: { sql: { postgres: { nativeType: 'jsonb' } } } } as const;
210213

211-
export class ArktypeJsonDescriptor extends CodecDescriptorImpl<ArktypeJsonTypeParams> {
214+
export class ArktypeJsonDescriptor extends PostgresCodecDescriptor<ArktypeJsonTypeParams> {
215+
protected override nativeType(): string {
216+
return ARKTYPE_JSON_NATIVE_TYPE;
217+
}
218+
protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr {
219+
return expression;
220+
}
212221
override readonly codecId = ARKTYPE_JSON_CODEC_ID;
213222
override readonly traits = ['equality'] as const;
214223
override readonly targetTypes = [ARKTYPE_JSON_NATIVE_TYPE] as const;
@@ -284,4 +293,4 @@ arktypeJsonColumn satisfies ColumnHelperFor<ArktypeJsonDescriptor>;
284293
*/
285294
export type ArktypeJsonCodec<TInferred> = ArktypeJsonCodecClass<TInferred>;
286295

287-
export const codecDescriptors: readonly AnyCodecDescriptor[] = [arktypeJsonDescriptor];
296+
export const codecDescriptors = definePostgresCodecs([arktypeJsonDescriptor]);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { CodecInstanceContext } from '@prisma-next/framework-components/codec';
2+
import type { AnyPostgresCodecDescriptor } from '@prisma-next/target-postgres/codec-descriptor';
3+
import { type } from 'arktype';
4+
import { expectTypeOf, test } from 'vitest';
5+
import {
6+
type ArktypeJsonCodecClass,
7+
type ArktypeJsonDescriptor,
8+
arktypeJsonColumn,
9+
arktypeJsonDescriptor,
10+
codecDescriptors,
11+
} from '../src/core/arktype-json-codec';
12+
13+
test('arktype-json canonical descriptors are PostgreSQL target descriptors', () => {
14+
expectTypeOf(codecDescriptors).toExtend<readonly AnyPostgresCodecDescriptor[]>();
15+
expectTypeOf<(typeof codecDescriptors)[number]>().toEqualTypeOf<ArktypeJsonDescriptor>();
16+
});
17+
18+
test('arktype-json preserves descriptor erasure and column-site schema inference', () => {
19+
const schema = type({ name: 'string', price: 'number' });
20+
const column = arktypeJsonColumn(schema);
21+
22+
expectTypeOf(arktypeJsonDescriptor.factory(column.typeParams)).toEqualTypeOf<
23+
(ctx: CodecInstanceContext) => ArktypeJsonCodecClass<unknown>
24+
>();
25+
expectTypeOf(column.codecFactory).toEqualTypeOf<
26+
(ctx: CodecInstanceContext) => ArktypeJsonCodecClass<{ name: string; price: number }>
27+
>();
28+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { JsonValue } from '@prisma-next/contract/types';
2+
import type { CodecRef } from '@prisma-next/framework-components/codec';
3+
import { ColumnRef } from '@prisma-next/sql-relational-core/ast';
4+
import { type } from 'arktype';
5+
import { describe, expect, it } from 'vitest';
6+
import {
7+
arktypeJsonColumn,
8+
arktypeJsonDescriptor,
9+
codecDescriptors,
10+
} from '../src/core/arktype-json-codec';
11+
import { arktypeJsonCodecRegistry } from '../src/core/registry';
12+
import { arktypeJsonExtensionDescriptor } from '../src/exports/control';
13+
import { arktypeJsonRuntimeDescriptor } from '../src/exports/runtime';
14+
15+
describe('arktype-json PostgreSQL codec descriptor adoption', () => {
16+
it('uses one target descriptor instance across canonical, registry, runtime, and control contributions', () => {
17+
expect(codecDescriptors).toEqual([arktypeJsonDescriptor]);
18+
expect(
19+
codecDescriptors.every((descriptor) => descriptor.descriptorKind === 'postgres-codec'),
20+
).toBe(true);
21+
expect([...arktypeJsonCodecRegistry.values()]).toEqual(codecDescriptors);
22+
expect(arktypeJsonRuntimeDescriptor.codecs()).toEqual(codecDescriptors);
23+
expect(arktypeJsonRuntimeDescriptor.types?.codecTypes?.codecDescriptors).toEqual(
24+
codecDescriptors,
25+
);
26+
expect(arktypeJsonExtensionDescriptor.types?.codecTypes?.codecDescriptors).toEqual(
27+
codecDescriptors,
28+
);
29+
});
30+
31+
it('preserves jsonb native type, identity projection, and structured JSON behavior', () => {
32+
const schema = type({ name: 'string', price: 'number' });
33+
const column = arktypeJsonColumn(schema);
34+
const ref: CodecRef = {
35+
codecId: arktypeJsonDescriptor.codecId,
36+
typeParams: {
37+
expression: column.typeParams.expression,
38+
jsonIr: column.typeParams.jsonIr as JsonValue,
39+
},
40+
};
41+
const expression = ColumnRef.of('products', 'details');
42+
43+
expect(arktypeJsonDescriptor.nativeTypeFor(ref)).toBe('jsonb');
44+
expect(arktypeJsonDescriptor.meta?.db?.sql?.postgres?.nativeType).toBe('jsonb');
45+
expect(arktypeJsonDescriptor.projectJson(expression, ref)).toBe(expression);
46+
47+
const codec = column.codecFactory({ name: 'details' });
48+
const value = { name: 'Widget', price: 9.99 };
49+
expect(codec.encodeJson(value)).toEqual(value);
50+
expect(codec.decodeJson(value)).toEqual(value);
51+
});
52+
});

packages/3-extensions/pgvector/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@prisma-next/sql-relational-core": "workspace:0.16.0",
2727
"@prisma-next/sql-runtime": "workspace:0.16.0",
2828
"@prisma-next/sql-schema-ir": "workspace:0.16.0",
29+
"@prisma-next/target-postgres": "workspace:0.16.0",
2930
"@prisma-next/utils": "workspace:0.16.0",
3031
"@standard-schema/spec": "^1.1.0",
3132
"arktype": "^2.2.2"
@@ -36,7 +37,6 @@
3637
"@prisma-next/operations": "workspace:0.16.0",
3738
"@prisma-next/postgres": "workspace:0.16.0",
3839
"@prisma-next/sql-contract-ts": "workspace:0.16.0",
39-
"@prisma-next/target-postgres": "workspace:0.16.0",
4040
"@prisma-next/test-utils": "workspace:0.16.0",
4141
"@prisma-next/tsconfig": "workspace:0.16.0",
4242
"@prisma-next/tsdown": "workspace:0.16.0",

packages/3-extensions/pgvector/src/core/codecs.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Mirrors the patterns in `postgres/codecs-class.ts` and `sqlite/codecs-class.ts` for the single `pg/vector@1` codec. Three artifacts:
55
*
66
* 1. `PgVectorCodec` extends {@link CodecImpl} with the runtime encode/decode/encodeJson/decodeJson conversions inline. Conversions are simple enough (PostgreSQL `[1,2,3]` text format) that no shared helper module is warranted; the class body is the source of truth.
7-
* 2. `PgVectorDescriptor` extends {@link CodecDescriptorImpl} with the codec id, traits, target types, params schema (`{ length: number }`, validated against {@link VECTOR_MAX_DIM}), `meta` (postgres `nativeType: 'vector'`), and the emit-path `renderOutputType` producing `Vector<${length}>`.
7+
* 2. `PgVectorDescriptor` extends {@link PostgresCodecDescriptor} with the codec id, traits, target types, params schema (`{ length: number }`, validated against {@link VECTOR_MAX_DIM}), `meta` (postgres `nativeType: 'vector'`), explicit target behavior, and the emit-path `renderOutputType` producing `Vector<${length}>`.
88
* 3. `pgVectorColumn(length)` per-codec column helper invoking `descriptor.factory({ length })` directly + passing the bare `nativeType: 'vector'`. The family-layer {@link expandNativeType} hook renders the parameterized form (`vector(1536)`) at emit/verify time from `nativeType` + `typeParams`.
99
*
1010
* `length` threads into the runtime codec via the constructor so encode/decode/encodeJson/decodeJson enforce the declared dimension at every ingress path. Without this, `vector(3)` and `vector(1536)` would produce codecs with identical behaviour and a dimension-mismatched value would round-trip undetected.
@@ -14,14 +14,17 @@ import type { JsonValue } from '@prisma-next/contract/types';
1414
import {
1515
type AnyCodecDescriptor,
1616
type CodecCallContext,
17-
CodecDescriptorImpl,
1817
CodecImpl,
1918
type CodecInstanceContext,
2019
type ColumnHelperFor,
2120
type ColumnHelperForStrict,
2221
column,
2322
} from '@prisma-next/framework-components/codec';
24-
import type { ExtractCodecTypes } from '@prisma-next/sql-relational-core/ast';
23+
import type { ExtractCodecTypes, ProjectionExpr } from '@prisma-next/sql-relational-core/ast';
24+
import {
25+
definePostgresCodecs,
26+
PostgresCodecDescriptor,
27+
} from '@prisma-next/target-postgres/codec-descriptor';
2528
import type { StandardSchemaV1 } from '@standard-schema/spec';
2629
import { type as arktype } from 'arktype';
2730
import { VECTOR_CODEC_ID, VECTOR_MAX_DIM } from './constants';
@@ -147,7 +150,13 @@ export class PgVectorCodec extends CodecImpl<
147150
}
148151
}
149152

150-
export class PgVectorDescriptor extends CodecDescriptorImpl<VectorParams> {
153+
export class PgVectorDescriptor extends PostgresCodecDescriptor<VectorParams> {
154+
protected override nativeType(): string {
155+
return PG_VECTOR_META.db.sql.postgres.nativeType;
156+
}
157+
protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr {
158+
return expression;
159+
}
151160
override readonly codecId = VECTOR_CODEC_ID;
152161
override readonly traits = ['equality'] as const;
153162
override readonly targetTypes = ['vector'] as const;
@@ -180,4 +189,4 @@ const codecDescriptorMap = {
180189

181190
export type CodecTypes = ExtractCodecTypes<typeof codecDescriptorMap>;
182191

183-
export const codecDescriptors: readonly AnyCodecDescriptor[] = Object.values(codecDescriptorMap);
192+
export const codecDescriptors = definePostgresCodecs(Object.values(codecDescriptorMap));
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import pgvector from '@prisma-next/extension-pgvector/pack';
2+
import { defineContract } from '@prisma-next/postgres/contract-builder';
3+
4+
export const pgvectorContract = defineContract({ extensions: { pgvector } }, ({ type }) => ({
5+
types: {
6+
Vector3: type.pgvector.Vector(3),
7+
},
8+
}));
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { ExtractCodecTypes } from '@prisma-next/sql-contract/types';
2+
import { expectTypeOf, test } from 'vitest';
3+
import { pgvectorContract } from './contract-declaration-portability.fixture';
4+
5+
type ContractCodecTypes = ExtractCodecTypes<typeof pgvectorContract>;
6+
type PgvectorCodecTypes = NonNullable<
7+
(typeof pgvectorContract.extensions.pgvector)['__codecTypes']
8+
>;
9+
10+
test('exported contracts retain pgvector public type metadata', () => {
11+
const extension = pgvectorContract.extensions.pgvector;
12+
13+
expectTypeOf(pgvectorContract.target).toEqualTypeOf<'postgres'>();
14+
expectTypeOf(pgvectorContract.targetFamily).toEqualTypeOf<'sql'>();
15+
expectTypeOf(extension.kind).toEqualTypeOf<'extension'>();
16+
expectTypeOf(extension.id).toEqualTypeOf<'pgvector'>();
17+
expectTypeOf(extension.familyId).toEqualTypeOf<'sql'>();
18+
expectTypeOf(extension.targetId).toEqualTypeOf<'postgres'>();
19+
expectTypeOf(extension.capabilities.postgres['pgvector.cosine']).toEqualTypeOf<true>();
20+
expectTypeOf(extension.authoring.type.pgvector.Vector.kind).toEqualTypeOf<'typeConstructor'>();
21+
expectTypeOf(
22+
extension.types.codecTypes.import.package,
23+
).toEqualTypeOf<'@prisma-next/extension-pgvector/codec-types'>();
24+
expectTypeOf<PgvectorCodecTypes['pg/vector@1']['input']>().toEqualTypeOf<number[]>();
25+
expectTypeOf<PgvectorCodecTypes['pg/vector@1']['output']>().toEqualTypeOf<number[]>();
26+
expectTypeOf<ContractCodecTypes['pg/vector@1']['input']>().toEqualTypeOf<number[]>();
27+
expectTypeOf<ContractCodecTypes['pg/vector@1']['output']>().toEqualTypeOf<number[]>();
28+
expectTypeOf<ContractCodecTypes['pg/vector@1']['traits']>().toEqualTypeOf<'equality'>();
29+
});

0 commit comments

Comments
 (0)