diff --git a/graphql/codegen/examples/example-postgis.schema.graphql b/graphql/codegen/examples/example-postgis.schema.graphql new file mode 100644 index 000000000..de6168584 --- /dev/null +++ b/graphql/codegen/examples/example-postgis.schema.graphql @@ -0,0 +1,305 @@ +"""A universally unique identifier as defined by [RFC 4122](https://tools.ietf.org/html/rfc4122).""" +scalar UUID + +""" +A point in time as described by the [ISO +8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone. +""" +scalar Datetime + +"""A string representing a cursor for pagination.""" +scalar Cursor + +"""GeoJSON scalar for PostGIS geometry input/output.""" +scalar GeoJSON + +"""The root query type.""" +type Query { + """Reads and enables pagination through a set of `Place`.""" + places( + first: Int + last: Int + offset: Int + before: Cursor + after: Cursor + orderBy: [PlacesOrderBy!] = [PRIMARY_KEY_ASC] + filter: PlaceFilter + condition: PlaceCondition + ): PlacesConnection + + """Reads a single `Place` using its globally unique `ID`.""" + place(id: UUID!): Place + + """Reads and enables pagination through a set of `Route`.""" + routes( + first: Int + last: Int + offset: Int + before: Cursor + after: Cursor + orderBy: [RoutesOrderBy!] = [PRIMARY_KEY_ASC] + filter: RouteFilter + condition: RouteCondition + ): RoutesConnection + + """Reads a single `Route` using its globally unique `ID`.""" + route(id: UUID!): Route +} + +"""The root mutation type.""" +type Mutation { + """Creates a single `Place`.""" + createPlace(input: CreatePlaceInput!): CreatePlacePayload + + """Updates a single `Place` using its globally unique `ID`.""" + updatePlace(input: UpdatePlaceInput!): UpdatePlacePayload + + """Deletes a single `Place` using its globally unique `ID`.""" + deletePlace(input: DeletePlaceInput!): DeletePlacePayload + + """Creates a single `Route`.""" + createRoute(input: CreateRouteInput!): CreateRoutePayload + + """Updates a single `Route` using its globally unique `ID`.""" + updateRoute(input: UpdateRouteInput!): UpdateRoutePayload + + """Deletes a single `Route` using its globally unique `ID`.""" + deleteRoute(input: DeleteRouteInput!): DeleteRoutePayload +} + +# ============================================================================ +# Entity Types +# ============================================================================ + +type Place { + id: UUID! + name: String! + location: GeoJSON + createdAt: Datetime! + updatedAt: Datetime +} + +type Route { + id: UUID! + name: String! + path: GeoJSON + createdAt: Datetime! +} + +# ============================================================================ +# Enums +# ============================================================================ + +enum PlacesOrderBy { + NATURAL + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + ID_ASC + ID_DESC + NAME_ASC + NAME_DESC + CREATED_AT_ASC + CREATED_AT_DESC +} + +enum RoutesOrderBy { + NATURAL + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + ID_ASC + ID_DESC + NAME_ASC + NAME_DESC + CREATED_AT_ASC + CREATED_AT_DESC +} + +# ============================================================================ +# Connection Types +# ============================================================================ + +type PlacesConnection { + nodes: [Place!]! + edges: [PlacesEdge!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type PlacesEdge { + node: Place! + cursor: Cursor! +} + +type RoutesConnection { + nodes: [Route!]! + edges: [RoutesEdge!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type RoutesEdge { + node: Route! + cursor: Cursor! +} + +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: Cursor + endCursor: Cursor +} + +# ============================================================================ +# Filter Types (geometry columns should use GeoJSONFilter) +# ============================================================================ + +input PlaceFilter { + id: UUIDFilter + name: StringFilter + location: GeoJSONFilter + and: [PlaceFilter!] + or: [PlaceFilter!] + not: PlaceFilter +} + +input RouteFilter { + id: UUIDFilter + name: StringFilter + path: GeoJSONFilter + and: [RouteFilter!] + or: [RouteFilter!] + not: RouteFilter +} + +input UUIDFilter { + equalTo: UUID + notEqualTo: UUID + in: [UUID!] + notIn: [UUID!] + isNull: Boolean +} + +input StringFilter { + equalTo: String + notEqualTo: String + in: [String!] + notIn: [String!] + includes: String + startsWith: String + endsWith: String + isNull: Boolean +} + +input GeoJSONFilter { + isNull: Boolean + equalTo: GeoJSON + notEqualTo: GeoJSON + distinctFrom: GeoJSON + notDistinctFrom: GeoJSON +} + +# ============================================================================ +# Condition Types +# ============================================================================ + +input PlaceCondition { + id: UUID + name: String + location: GeoJSON +} + +input RouteCondition { + id: UUID + name: String + path: GeoJSON +} + +# ============================================================================ +# Mutation Input Types +# ============================================================================ + +input CreatePlaceInput { + clientMutationId: String + place: PlaceInput! +} + +input PlaceInput { + name: String! + location: GeoJSON +} + +input UpdatePlaceInput { + clientMutationId: String + id: UUID! + patch: PlacePatch! +} + +input PlacePatch { + name: String + location: GeoJSON +} + +input DeletePlaceInput { + clientMutationId: String + id: UUID! +} + +input CreateRouteInput { + clientMutationId: String + route: RouteInput! +} + +input RouteInput { + name: String! + path: GeoJSON +} + +input UpdateRouteInput { + clientMutationId: String + id: UUID! + patch: RoutePatch! +} + +input RoutePatch { + name: String + path: GeoJSON +} + +input DeleteRouteInput { + clientMutationId: String + id: UUID! +} + +# ============================================================================ +# Mutation Payload Types +# ============================================================================ + +type CreatePlacePayload { + clientMutationId: String + place: Place +} + +type UpdatePlacePayload { + clientMutationId: String + place: Place +} + +type DeletePlacePayload { + clientMutationId: String + place: Place +} + +type CreateRoutePayload { + clientMutationId: String + route: Route +} + +type UpdateRoutePayload { + clientMutationId: String + route: Route +} + +type DeleteRoutePayload { + clientMutationId: String + route: Route +} diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap index afa9eb471..48524270e 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap @@ -173,6 +173,13 @@ export interface VectorFilter { distinctFrom?: number[]; notDistinctFrom?: number[]; } +export interface GeoJSONFilter { + isNull?: boolean; + equalTo?: unknown; + notEqualTo?: unknown; + distinctFrom?: unknown; + notDistinctFrom?: unknown; +} export interface StringListFilter { isNull?: boolean; equalTo?: string[]; @@ -662,6 +669,13 @@ export interface VectorFilter { distinctFrom?: number[]; notDistinctFrom?: number[]; } +export interface GeoJSONFilter { + isNull?: boolean; + equalTo?: unknown; + notEqualTo?: unknown; + distinctFrom?: unknown; + notDistinctFrom?: unknown; +} export interface StringListFilter { isNull?: boolean; equalTo?: string[]; @@ -987,6 +1001,13 @@ export interface VectorFilter { distinctFrom?: number[]; notDistinctFrom?: number[]; } +export interface GeoJSONFilter { + isNull?: boolean; + equalTo?: unknown; + notEqualTo?: unknown; + distinctFrom?: unknown; + notDistinctFrom?: unknown; +} export interface StringListFilter { isNull?: boolean; equalTo?: string[]; @@ -1324,6 +1345,13 @@ export interface VectorFilter { distinctFrom?: number[]; notDistinctFrom?: number[]; } +export interface GeoJSONFilter { + isNull?: boolean; + equalTo?: unknown; + notEqualTo?: unknown; + distinctFrom?: unknown; + notDistinctFrom?: unknown; +} export interface StringListFilter { isNull?: boolean; equalTo?: string[]; @@ -1666,6 +1694,13 @@ export interface VectorFilter { distinctFrom?: number[]; notDistinctFrom?: number[]; } +export interface GeoJSONFilter { + isNull?: boolean; + equalTo?: unknown; + notEqualTo?: unknown; + distinctFrom?: unknown; + notDistinctFrom?: unknown; +} export interface StringListFilter { isNull?: boolean; equalTo?: string[]; @@ -2064,6 +2099,13 @@ export interface VectorFilter { distinctFrom?: number[]; notDistinctFrom?: number[]; } +export interface GeoJSONFilter { + isNull?: boolean; + equalTo?: unknown; + notEqualTo?: unknown; + distinctFrom?: unknown; + notDistinctFrom?: unknown; +} export interface StringListFilter { isNull?: boolean; equalTo?: string[]; @@ -2458,6 +2500,13 @@ export interface VectorFilter { distinctFrom?: number[]; notDistinctFrom?: number[]; } +export interface GeoJSONFilter { + isNull?: boolean; + equalTo?: unknown; + notEqualTo?: unknown; + distinctFrom?: unknown; + notDistinctFrom?: unknown; +} export interface StringListFilter { isNull?: boolean; equalTo?: string[]; diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap index 89631177b..d144c87ca 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap @@ -2138,7 +2138,7 @@ exports[`Schema Types Generator generateSchemaTypesFile generates schema types f * DO NOT EDIT - changes will be overwritten */ -import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; +import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, GeoJSONFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; export type UserRole = "ADMIN" | "USER" | "GUEST"; export interface RegisterInput { email: string; @@ -2165,7 +2165,7 @@ exports[`Schema Types Generator generateSchemaTypesFile generates schema types f * DO NOT EDIT - changes will be overwritten */ -import type { User, BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; +import type { User, BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, GeoJSONFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; export type UserRole = "ADMIN" | "USER" | "GUEST"; export interface RegisterInput { email: string; diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/schema-types-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/schema-types-generator.test.ts.snap index 4115d64a6..95ab49c80 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/schema-types-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/schema-types-generator.test.ts.snap @@ -7,7 +7,7 @@ exports[`schema-types-generator generates enum types as string unions 1`] = ` * DO NOT EDIT - changes will be overwritten */ -import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; +import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, GeoJSONFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; export type Status = "ACTIVE" | "INACTIVE" | "PENDING"; export type Priority = "LOW" | "MEDIUM" | "HIGH";" `; @@ -19,7 +19,7 @@ exports[`schema-types-generator generates input object types as interfaces 1`] = * DO NOT EDIT - changes will be overwritten */ -import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; +import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, GeoJSONFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; export interface CreateUserInput { email: string; name?: string; @@ -38,7 +38,7 @@ exports[`schema-types-generator generates payload types from mutation return typ * DO NOT EDIT - changes will be overwritten */ -import type { User, BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; +import type { User, BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, GeoJSONFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; export interface LoginPayload { token: string; refreshToken?: string | null; @@ -53,7 +53,7 @@ exports[`schema-types-generator generates union types 1`] = ` * DO NOT EDIT - changes will be overwritten */ -import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; +import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, GeoJSONFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; export type SearchResult = User | Post | Comment;" `; @@ -64,6 +64,6 @@ exports[`schema-types-generator skips table types and standard scalars 1`] = ` * DO NOT EDIT - changes will be overwritten */ -import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; +import type { BigFloatFilter, BigIntFilter, BitStringFilter, BooleanFilter, DateFilter, DatetimeFilter, FloatFilter, FullTextFilter, GeoJSONFilter, IntFilter, IntListFilter, InternetAddressFilter, JSONFilter, StringFilter, StringListFilter, UUIDFilter, UUIDListFilter, VectorFilter } from "./types"; export type CustomEnum = "VALUE_A" | "VALUE_B";" `; diff --git a/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts b/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts index 577134398..09abf947e 100644 --- a/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts +++ b/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts @@ -467,6 +467,19 @@ describe('scalar filter types', () => { expect(result.content).toContain('distinctFrom?: number[];'); expect(result.content).toContain('notDistinctFrom?: number[];'); }); + + it('includes GeoJSONFilter for PostGIS geometry fields', () => { + const result = generateInputTypesFile(new Map(), new Set(), [userTable]); + + // GeoJSONFilter should be generated as a scalar filter type + expect(result.content).toContain('export interface GeoJSONFilter {'); + expect(result.content).toContain('isNull?: boolean;'); + // GeoJSON maps to unknown, so equality operators use unknown + expect(result.content).toContain('equalTo?: unknown;'); + expect(result.content).toContain('notEqualTo?: unknown;'); + expect(result.content).toContain('distinctFrom?: unknown;'); + expect(result.content).toContain('notDistinctFrom?: unknown;'); + }); }); // ============================================================================ diff --git a/graphql/codegen/src/__tests__/codegen/postgis-codegen-integration.test.ts b/graphql/codegen/src/__tests__/codegen/postgis-codegen-integration.test.ts new file mode 100644 index 000000000..f82a196da --- /dev/null +++ b/graphql/codegen/src/__tests__/codegen/postgis-codegen-integration.test.ts @@ -0,0 +1,183 @@ +/** + * Integration test for PostGIS codegen support. + * + * Uses the example-postgis.schema.graphql fixture which includes: + * - GeoJSON scalar type (for geometry/geography columns) + * - GeoJSONFilter in filter types (equality/distinct operators) + * - Geometry columns mapped to unknown in entity types + * + * This test exercises the full codegen pipeline (schema file → introspection → + * tables + typeRegistry → generated TypeScript) to verify that PostGIS geometry + * types are correctly included in the generated output. + */ +import path from 'node:path'; + +import { getConfigOptions } from '../../types/config'; +import { generateOrm } from '../../core/codegen/orm'; +import { runCodegenPipeline } from '../../core/pipeline'; +import { FileSchemaSource } from '../../core/introspect/source/file'; + +const POSTGIS_SCHEMA_PATH = path.resolve( + __dirname, + '../../..', + 'examples/example-postgis.schema.graphql', +); + +describe('postgis codegen integration', () => { + let pipelineResult: Awaited>; + + beforeAll(async () => { + const source = new FileSchemaSource({ schemaPath: POSTGIS_SCHEMA_PATH }); + const config = getConfigOptions({ orm: true, output: '/tmp/test-output' }); + + pipelineResult = await runCodegenPipeline({ + source, + config, + }); + }); + + // ======================================================================== + // Pipeline sanity checks + // ======================================================================== + + it('infers Place and Route tables from the PostGIS schema', () => { + const tableNames = pipelineResult.tables.map((t) => t.name); + expect(tableNames).toContain('Place'); + expect(tableNames).toContain('Route'); + }); + + it('Place table has a GeoJSON location field', () => { + const placeTable = pipelineResult.tables.find((t) => t.name === 'Place'); + expect(placeTable).toBeDefined(); + const locationField = placeTable!.fields.find( + (f) => f.name === 'location', + ); + expect(locationField).toBeDefined(); + // Field type may be a string or an object with gqlType + const fieldType = typeof locationField!.type === 'string' + ? locationField!.type + : (locationField!.type as any).gqlType; + expect(fieldType).toBe('GeoJSON'); + }); + + it('Route table has a GeoJSON path field', () => { + const routeTable = pipelineResult.tables.find((t) => t.name === 'Route'); + expect(routeTable).toBeDefined(); + const pathField = routeTable!.fields.find((f) => f.name === 'path'); + expect(pathField).toBeDefined(); + const fieldType = typeof pathField!.type === 'string' + ? pathField!.type + : (pathField!.type as any).gqlType; + expect(fieldType).toBe('GeoJSON'); + }); + + // ======================================================================== + // Full ORM generation + // ======================================================================== + + describe('generated ORM output', () => { + let inputTypesContent: string; + + beforeAll(() => { + const { tables, customOperations } = pipelineResult; + const config = getConfigOptions({ orm: true, output: '/tmp/test-output' }); + + const result = generateOrm({ + tables, + customOperations, + config, + }); + + const inputTypesFile = result.files.find((f) => + f.path.includes('input-types'), + ); + expect(inputTypesFile).toBeDefined(); + inputTypesContent = inputTypesFile!.content; + }); + + // === GeoJSON scalar mapping === + + it('maps GeoJSON scalar to unknown in entity types', () => { + expect(inputTypesContent).toContain('export interface Place {'); + // GeoJSON → unknown + expect(inputTypesContent).toContain('location?: unknown | null;'); + }); + + it('maps GeoJSON scalar to unknown in Route entity', () => { + expect(inputTypesContent).toContain('export interface Route {'); + expect(inputTypesContent).toContain('path?: unknown | null;'); + }); + + // === GeoJSONFilter in filter types === + + it('generates GeoJSONFilter interface', () => { + expect(inputTypesContent).toContain('export interface GeoJSONFilter {'); + expect(inputTypesContent).toContain('isNull?: boolean;'); + expect(inputTypesContent).toContain('equalTo?: unknown;'); + expect(inputTypesContent).toContain('notEqualTo?: unknown;'); + expect(inputTypesContent).toContain('distinctFrom?: unknown;'); + expect(inputTypesContent).toContain('notDistinctFrom?: unknown;'); + }); + + it('uses GeoJSONFilter for geometry columns in PlaceFilter', () => { + expect(inputTypesContent).toContain('export interface PlaceFilter {'); + // The location field should reference GeoJSONFilter + expect(inputTypesContent).toMatch(/location\?.*GeoJSONFilter/); + }); + + it('uses GeoJSONFilter for geometry columns in RouteFilter', () => { + expect(inputTypesContent).toContain('export interface RouteFilter {'); + // The path field should reference GeoJSONFilter + expect(inputTypesContent).toMatch(/path\?.*GeoJSONFilter/); + }); + + // === GeoJSON in condition types === + + it('generates PlaceCondition with GeoJSON location field', () => { + expect(inputTypesContent).toContain( + 'export interface PlaceCondition {', + ); + expect(inputTypesContent).toMatch(/location\?.*unknown.*null/); + }); + + // === GeoJSON in create/patch input types === + + it('includes GeoJSON fields in create input types', () => { + expect(inputTypesContent).toContain('export interface CreatePlaceInput {'); + expect(inputTypesContent).toContain('export interface CreateRouteInput {'); + // GeoJSON location field should appear as unknown in create inputs + expect(inputTypesContent).toMatch(/location\?.*unknown/); + }); + + it('includes GeoJSON fields in patch types', () => { + expect(inputTypesContent).toContain('export interface PlacePatch {'); + expect(inputTypesContent).toContain('export interface RoutePatch {'); + }); + + // === Backwards compatibility === + + it('still generates standard CRUD input types correctly', () => { + expect(inputTypesContent).toContain( + 'export interface CreatePlaceInput {', + ); + expect(inputTypesContent).toContain( + 'export interface UpdatePlaceInput {', + ); + expect(inputTypesContent).toContain( + 'export interface DeletePlaceInput {', + ); + }); + + it('still generates standard filter types correctly', () => { + expect(inputTypesContent).toContain( + 'export interface PlaceFilter {', + ); + expect(inputTypesContent).toContain( + 'export interface RouteFilter {', + ); + // Standard scalar filters should still exist + expect(inputTypesContent).toContain('export interface StringFilter {'); + expect(inputTypesContent).toContain('export interface UUIDFilter {'); + }); + }); +}); diff --git a/graphql/codegen/src/core/codegen/orm/input-types-generator.ts b/graphql/codegen/src/core/codegen/orm/input-types-generator.ts index 4a765433b..7c3f96f4c 100644 --- a/graphql/codegen/src/core/codegen/orm/input-types-generator.ts +++ b/graphql/codegen/src/core/codegen/orm/input-types-generator.ts @@ -327,6 +327,8 @@ const SCALAR_FILTER_CONFIGS: ScalarFilterConfig[] = [ // connection-filter may still generate a filter for vector columns. This ensures // the generated type uses number[] rather than being silently omitted. { name: 'VectorFilter', tsType: 'number[]', operators: ['equality', 'distinct'] }, + // PostGIS geometry filter (for geometry/geography/GeoJSON fields) + { name: 'GeoJSONFilter', tsType: 'unknown', operators: ['equality', 'distinct'] }, // List filters (for array fields like string[], int[], uuid[]) { name: 'StringListFilter', diff --git a/graphql/codegen/src/core/codegen/scalars.ts b/graphql/codegen/src/core/codegen/scalars.ts index a49d33170..2f89d0729 100644 --- a/graphql/codegen/src/core/codegen/scalars.ts +++ b/graphql/codegen/src/core/codegen/scalars.ts @@ -69,6 +69,11 @@ export const SCALAR_FILTER_MAP: Record = { Vector: 'VectorFilter', FullText: 'FullTextFilter', Interval: 'StringFilter', + // PostGIS geometry types + GeoJSON: 'GeoJSONFilter', + Geometry: 'GeoJSONFilter', + GeometryPoint: 'GeoJSONFilter', + Point: 'GeoJSONFilter', }; export const SCALAR_NAMES = new Set(Object.keys(SCALAR_TS_MAP));