Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Branded<T, Shape extends Record<string, unknown>> = T & {
};

type BrandedString<Shape extends Record<string, unknown>> = Branded<string, Shape>;
type BrandedDate<Shape extends Record<string, unknown>> = Branded<Date, Shape>;

export type Char<N extends number> = BrandedString<{ __charLength: N }>;
export type Varchar<N extends number> = BrandedString<{ __varcharLength: N }>;
Expand All @@ -36,10 +37,14 @@ export type Numeric<P extends number, S extends number | undefined = undefined>
}>;
export type Bit<N extends number> = BrandedString<{ __bitLength: N }>;
export type VarBit<N extends number> = BrandedString<{ __varbitLength: N }>;
export type Timestamp<P extends number | undefined = undefined> = BrandedString<{
// `Timestamp` / `Timestamptz` brand `Date` — the `pg/timestamp@1` and
// `pg/timestamptz@1` codecs decode to `Date`. Branding `string` here would
// contradict the codec's declared input/output and force consumers to cast
// before calling Date methods on a projected column.
export type Timestamp<P extends number | undefined = undefined> = BrandedDate<{
__timestampPrecision: P;
}>;
export type Timestamptz<P extends number | undefined = undefined> = BrandedString<{
export type Timestamptz<P extends number | undefined = undefined> = BrandedDate<{
__timestamptzPrecision: P;
}>;
export type Time<P extends number | undefined = undefined> = BrandedString<{ __timePrecision: P }>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { CodecInput } from '@prisma-next/sql-relational-core/ast';
import { expectTypeOf, test } from 'vitest';
import type { codecDefinitions } from '../src/core/codecs';
import type {
Char,
Numeric,
Time,
Timestamp,
Timestamptz,
Varchar,
} from '../src/exports/codec-types';

// Branded aliases must agree with their codec's declared input/output type.
// `pgTimestamp(tz)Codec` decodes to `Date`, so `Timestamp<P>` / `Timestamptz<P>`
// must be Date-shaped — calling Date methods on a projected column must
// typecheck without casts. See `core/codecs.ts:332-400`.

test('Timestamp<P> brand is Date-shaped', () => {
expectTypeOf<Timestamp<3>>().toExtend<Date>();
expectTypeOf<Timestamp>().toExtend<Date>();
expectTypeOf<Timestamp<3>>().not.toExtend<string>();
});

test('Timestamptz<P> brand is Date-shaped', () => {
expectTypeOf<Timestamptz<6>>().toExtend<Date>();
expectTypeOf<Timestamptz>().toExtend<Date>();
expectTypeOf<Timestamptz<6>>().not.toExtend<string>();
});

test('Timestamp/Timestamptz brand agrees with codec input/output type', () => {
type TsInput = CodecInput<typeof codecDefinitions.timestamp.codec>;
type TstzInput = CodecInput<typeof codecDefinitions.timestamptz.codec>;
expectTypeOf<TsInput>().toEqualTypeOf<Date>();
expectTypeOf<TstzInput>().toEqualTypeOf<Date>();
expectTypeOf<Timestamp<3>>().toExtend<TsInput>();
expectTypeOf<Timestamptz<6>>().toExtend<TstzInput>();
});

// Sanity: the other parameterized aliases stay string-shaped, because
// their codecs decode to string.

test('string-shaped parameterized aliases are unchanged', () => {
expectTypeOf<Char<16>>().toExtend<string>();
expectTypeOf<Varchar<255>>().toExtend<string>();
expectTypeOf<Numeric<10, 2>>().toExtend<string>();
expectTypeOf<Time<3>>().toExtend<string>();
});
Loading