graphile-postgis 1.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +22 -45
  2. package/constants.d.ts +1 -0
  3. package/constants.js +10 -1
  4. package/esm/constants.d.ts +13 -0
  5. package/esm/constants.js +9 -0
  6. package/esm/index.d.ts +24 -0
  7. package/esm/index.js +25 -33
  8. package/esm/plugins/codec.d.ts +19 -0
  9. package/esm/plugins/codec.js +174 -0
  10. package/esm/plugins/detect-extension.d.ts +14 -0
  11. package/esm/plugins/detect-extension.js +57 -0
  12. package/esm/plugins/geometry-fields.d.ts +21 -0
  13. package/esm/plugins/geometry-fields.js +245 -0
  14. package/esm/plugins/inflection.d.ts +8 -0
  15. package/esm/plugins/inflection.js +52 -0
  16. package/esm/plugins/register-types.d.ts +22 -0
  17. package/esm/plugins/register-types.js +319 -0
  18. package/esm/preset.d.ts +18 -0
  19. package/esm/preset.js +30 -0
  20. package/esm/types.d.ts +84 -0
  21. package/esm/utils.d.ts +21 -0
  22. package/esm/utils.js +18 -7
  23. package/index.d.ts +24 -15
  24. package/index.js +39 -47
  25. package/package.json +23 -18
  26. package/plugins/codec.d.ts +19 -0
  27. package/plugins/codec.js +180 -0
  28. package/plugins/detect-extension.d.ts +14 -0
  29. package/plugins/detect-extension.js +60 -0
  30. package/plugins/geometry-fields.d.ts +21 -0
  31. package/plugins/geometry-fields.js +248 -0
  32. package/plugins/inflection.d.ts +8 -0
  33. package/plugins/inflection.js +55 -0
  34. package/plugins/register-types.d.ts +22 -0
  35. package/plugins/register-types.js +325 -0
  36. package/preset.d.ts +18 -0
  37. package/preset.js +33 -0
  38. package/types.d.ts +69 -44
  39. package/utils.d.ts +16 -0
  40. package/utils.js +17 -6
  41. package/PostgisExtensionDetectionPlugin.d.ts +0 -3
  42. package/PostgisExtensionDetectionPlugin.js +0 -28
  43. package/PostgisInflectionPlugin.d.ts +0 -3
  44. package/PostgisInflectionPlugin.js +0 -36
  45. package/PostgisRegisterTypesPlugin.d.ts +0 -3
  46. package/PostgisRegisterTypesPlugin.js +0 -234
  47. package/PostgisVersionPlugin.d.ts +0 -3
  48. package/PostgisVersionPlugin.js +0 -24
  49. package/Postgis_GeometryCollection_GeometriesPlugin.d.ts +0 -3
  50. package/Postgis_GeometryCollection_GeometriesPlugin.js +0 -43
  51. package/Postgis_LineString_PointsPlugin.d.ts +0 -3
  52. package/Postgis_LineString_PointsPlugin.js +0 -40
  53. package/Postgis_MultiLineString_LineStringsPlugin.d.ts +0 -3
  54. package/Postgis_MultiLineString_LineStringsPlugin.js +0 -38
  55. package/Postgis_MultiPoint_PointsPlugin.d.ts +0 -3
  56. package/Postgis_MultiPoint_PointsPlugin.js +0 -38
  57. package/Postgis_MultiPolygon_PolygonsPlugin.d.ts +0 -3
  58. package/Postgis_MultiPolygon_PolygonsPlugin.js +0 -38
  59. package/Postgis_Point_LatitudeLongitudePlugin.d.ts +0 -3
  60. package/Postgis_Point_LatitudeLongitudePlugin.js +0 -43
  61. package/Postgis_Polygon_RingsPlugin.d.ts +0 -3
  62. package/Postgis_Polygon_RingsPlugin.js +0 -49
  63. package/esm/PostgisExtensionDetectionPlugin.js +0 -26
  64. package/esm/PostgisInflectionPlugin.js +0 -34
  65. package/esm/PostgisRegisterTypesPlugin.js +0 -229
  66. package/esm/PostgisVersionPlugin.js +0 -22
  67. package/esm/Postgis_GeometryCollection_GeometriesPlugin.js +0 -41
  68. package/esm/Postgis_LineString_PointsPlugin.js +0 -38
  69. package/esm/Postgis_MultiLineString_LineStringsPlugin.js +0 -36
  70. package/esm/Postgis_MultiPoint_PointsPlugin.js +0 -36
  71. package/esm/Postgis_MultiPolygon_PolygonsPlugin.js +0 -36
  72. package/esm/Postgis_Point_LatitudeLongitudePlugin.js +0 -41
  73. package/esm/Postgis_Polygon_RingsPlugin.js +0 -47
  74. package/esm/makeGeoJSONType.js +0 -39
  75. package/makeGeoJSONType.d.ts +0 -1
  76. package/makeGeoJSONType.js +0 -42
@@ -0,0 +1,245 @@
1
+ import 'graphile-build';
2
+ import 'graphile-build-pg';
3
+ import { GisSubtype } from '../constants';
4
+ import { getGISTypeName } from '../utils';
5
+ // Import types.ts for Build/Inflection/Scope augmentation side effects
6
+ import '../types';
7
+ /**
8
+ * PostgisGeometryFieldsPlugin
9
+ *
10
+ * Enhances PostGIS geometry object types with subtype-specific fields:
11
+ *
12
+ * - Point: x/longitude, y/latitude, optional z/height
13
+ * - LineString: points array
14
+ * - Polygon: exterior ring, interiors array
15
+ * - MultiPoint: points array
16
+ * - MultiLineString: lines array
17
+ * - MultiPolygon: polygons array
18
+ * - GeometryCollection: geometries array (uses dimension interface)
19
+ *
20
+ * Uses the GraphQLObjectType_fields hook to add fields based on the
21
+ * type's scope (isPgGISType, pgGISCodecName, pgGISTypeDetails).
22
+ */
23
+ export const PostgisGeometryFieldsPlugin = {
24
+ name: 'PostgisGeometryFieldsPlugin',
25
+ version: '2.0.0',
26
+ description: 'Adds subtype-specific fields to PostGIS geometry types',
27
+ after: ['PostgisRegisterTypesPlugin', 'PostgisInflectionPlugin'],
28
+ schema: {
29
+ hooks: {
30
+ GraphQLObjectType_fields(fields, build, context) {
31
+ const { isPgGISType, pgGISCodecName, pgGISTypeDetails } = context.scope;
32
+ if (!isPgGISType || !pgGISCodecName || !pgGISTypeDetails) {
33
+ return fields;
34
+ }
35
+ const { graphql: { GraphQLNonNull, GraphQLFloat, GraphQLList }, inflection } = build;
36
+ const typeDetails = pgGISTypeDetails;
37
+ const { subtype, hasZ, hasM, srid } = typeDetails;
38
+ const getType = build.getPostgisTypeByGeometryType;
39
+ switch (subtype) {
40
+ case GisSubtype.Point:
41
+ return addPointFields(fields, build, pgGISCodecName, hasZ, GraphQLNonNull, GraphQLFloat, inflection);
42
+ case GisSubtype.LineString:
43
+ return addLineStringFields(fields, build, pgGISCodecName, hasZ, hasM, srid, GraphQLList, getType);
44
+ case GisSubtype.Polygon:
45
+ return addPolygonFields(fields, build, pgGISCodecName, hasZ, hasM, srid, GraphQLList, getType);
46
+ case GisSubtype.MultiPoint:
47
+ return addMultiPointFields(fields, build, pgGISCodecName, hasZ, hasM, srid, GraphQLList, getType);
48
+ case GisSubtype.MultiLineString:
49
+ return addMultiLineStringFields(fields, build, pgGISCodecName, hasZ, hasM, srid, GraphQLList, getType);
50
+ case GisSubtype.MultiPolygon:
51
+ return addMultiPolygonFields(fields, build, pgGISCodecName, hasZ, hasM, srid, GraphQLList, getType);
52
+ case GisSubtype.GeometryCollection:
53
+ return addGeometryCollectionFields(fields, build, pgGISCodecName, hasZ, hasM, GraphQLList);
54
+ default:
55
+ return fields;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ };
61
+ function addPointFields(fields, build, codecName, hasZ, GraphQLNonNull, GraphQLFloat, inflection) {
62
+ const xFieldName = inflection.gisXFieldName(codecName);
63
+ const yFieldName = inflection.gisYFieldName(codecName);
64
+ const zFieldName = inflection.gisZFieldName(codecName);
65
+ const newFields = {
66
+ [xFieldName]: {
67
+ type: new GraphQLNonNull(GraphQLFloat),
68
+ resolve(data) {
69
+ const coords = data.__geojson.coordinates;
70
+ if (coords.length < 1) {
71
+ throw new Error('Point geometry has no x coordinate');
72
+ }
73
+ return coords[0];
74
+ }
75
+ },
76
+ [yFieldName]: {
77
+ type: new GraphQLNonNull(GraphQLFloat),
78
+ resolve(data) {
79
+ const coords = data.__geojson.coordinates;
80
+ if (coords.length < 2) {
81
+ throw new Error('Point geometry has no y coordinate');
82
+ }
83
+ return coords[1];
84
+ }
85
+ }
86
+ };
87
+ if (hasZ) {
88
+ newFields[zFieldName] = {
89
+ type: new GraphQLNonNull(GraphQLFloat),
90
+ resolve(data) {
91
+ const coords = data.__geojson.coordinates;
92
+ if (coords.length < 3) {
93
+ throw new Error('Point geometry has no z coordinate');
94
+ }
95
+ return coords[2];
96
+ }
97
+ };
98
+ }
99
+ return build.extend(fields, newFields, 'PostgisGeometryFieldsPlugin adding Point fields');
100
+ }
101
+ function addLineStringFields(fields, build, codecName, hasZ, hasM, srid, GraphQLList, getType) {
102
+ const PointType = getType?.(codecName, GisSubtype.Point, hasZ, hasM, srid);
103
+ if (!PointType)
104
+ return fields;
105
+ return build.extend(fields, {
106
+ points: {
107
+ type: new GraphQLList(PointType),
108
+ resolve(data) {
109
+ const lineString = data.__geojson;
110
+ return lineString.coordinates.map((coord) => ({
111
+ __gisType: getGISTypeName(GisSubtype.Point, hasZ, hasM),
112
+ __srid: data.__srid,
113
+ __geojson: {
114
+ type: 'Point',
115
+ coordinates: coord
116
+ }
117
+ }));
118
+ }
119
+ }
120
+ }, 'PostgisGeometryFieldsPlugin adding LineString fields');
121
+ }
122
+ function addPolygonFields(fields, build, codecName, hasZ, hasM, srid, GraphQLList, getType) {
123
+ const LineStringType = getType?.(codecName, GisSubtype.LineString, hasZ, hasM, srid);
124
+ if (!LineStringType)
125
+ return fields;
126
+ return build.extend(fields, {
127
+ exterior: {
128
+ type: LineStringType,
129
+ resolve(data) {
130
+ const polygon = data.__geojson;
131
+ return {
132
+ __gisType: getGISTypeName(GisSubtype.LineString, hasZ, hasM),
133
+ __srid: data.__srid,
134
+ __geojson: {
135
+ type: 'LineString',
136
+ coordinates: polygon.coordinates[0]
137
+ }
138
+ };
139
+ }
140
+ },
141
+ interiors: {
142
+ type: new GraphQLList(LineStringType),
143
+ resolve(data) {
144
+ const polygon = data.__geojson;
145
+ return polygon.coordinates.slice(1).map((coord) => ({
146
+ __gisType: getGISTypeName(GisSubtype.LineString, hasZ, hasM),
147
+ __srid: data.__srid,
148
+ __geojson: {
149
+ type: 'LineString',
150
+ coordinates: coord
151
+ }
152
+ }));
153
+ }
154
+ }
155
+ }, 'PostgisGeometryFieldsPlugin adding Polygon fields');
156
+ }
157
+ function addMultiPointFields(fields, build, codecName, hasZ, hasM, srid, GraphQLList, getType) {
158
+ const PointType = getType?.(codecName, GisSubtype.Point, hasZ, hasM, srid);
159
+ if (!PointType)
160
+ return fields;
161
+ return build.extend(fields, {
162
+ points: {
163
+ type: new GraphQLList(PointType),
164
+ resolve(data) {
165
+ const multiPoint = data.__geojson;
166
+ return multiPoint.coordinates.map((coord) => ({
167
+ __gisType: getGISTypeName(GisSubtype.Point, hasZ, hasM),
168
+ __srid: data.__srid,
169
+ __geojson: {
170
+ type: 'Point',
171
+ coordinates: coord
172
+ }
173
+ }));
174
+ }
175
+ }
176
+ }, 'PostgisGeometryFieldsPlugin adding MultiPoint fields');
177
+ }
178
+ function addMultiLineStringFields(fields, build, codecName, hasZ, hasM, srid, GraphQLList, getType) {
179
+ const LineStringType = getType?.(codecName, GisSubtype.LineString, hasZ, hasM, srid);
180
+ if (!LineStringType)
181
+ return fields;
182
+ return build.extend(fields, {
183
+ lines: {
184
+ type: new GraphQLList(LineStringType),
185
+ resolve(data) {
186
+ const multiLineString = data.__geojson;
187
+ return multiLineString.coordinates.map((coord) => ({
188
+ __gisType: getGISTypeName(GisSubtype.LineString, hasZ, hasM),
189
+ __srid: data.__srid,
190
+ __geojson: {
191
+ type: 'LineString',
192
+ coordinates: coord
193
+ }
194
+ }));
195
+ }
196
+ }
197
+ }, 'PostgisGeometryFieldsPlugin adding MultiLineString fields');
198
+ }
199
+ function addMultiPolygonFields(fields, build, codecName, hasZ, hasM, srid, GraphQLList, getType) {
200
+ const PolygonType = getType?.(codecName, GisSubtype.Polygon, hasZ, hasM, srid);
201
+ if (!PolygonType)
202
+ return fields;
203
+ return build.extend(fields, {
204
+ polygons: {
205
+ type: new GraphQLList(PolygonType),
206
+ resolve(data) {
207
+ const multiPolygon = data.__geojson;
208
+ return multiPolygon.coordinates.map((coord) => ({
209
+ __gisType: getGISTypeName(GisSubtype.Polygon, hasZ, hasM),
210
+ __srid: data.__srid,
211
+ __geojson: {
212
+ type: 'Polygon',
213
+ coordinates: coord
214
+ }
215
+ }));
216
+ }
217
+ }
218
+ }, 'PostgisGeometryFieldsPlugin adding MultiPolygon fields');
219
+ }
220
+ function addGeometryCollectionFields(fields, build, codecName, hasZ, hasM, GraphQLList) {
221
+ const dimInterfaceName = build.inflection.gisDimensionInterfaceName(codecName, hasZ, hasM);
222
+ const Interface = build.getTypeByName(dimInterfaceName);
223
+ if (!Interface) {
224
+ return fields;
225
+ }
226
+ return build.extend(fields, {
227
+ geometries: {
228
+ type: new GraphQLList(Interface),
229
+ resolve(data) {
230
+ const geometryCollection = data.__geojson;
231
+ return geometryCollection.geometries.map((geom) => {
232
+ const subtypeValue = GisSubtype[geom.type];
233
+ if (subtypeValue === undefined) {
234
+ throw new Error(`Unsupported geometry subtype ${geom.type}`);
235
+ }
236
+ return {
237
+ __gisType: getGISTypeName(subtypeValue, hasZ, hasM),
238
+ __srid: data.__srid,
239
+ __geojson: geom
240
+ };
241
+ });
242
+ }
243
+ }
244
+ }, 'PostgisGeometryFieldsPlugin adding GeometryCollection fields');
245
+ }
@@ -0,0 +1,8 @@
1
+ import type { GraphileConfig } from 'graphile-config';
2
+ import '../types';
3
+ /**
4
+ * PostgisInflectionPlugin
5
+ *
6
+ * Adds inflection methods for generating PostGIS-related GraphQL type names.
7
+ */
8
+ export declare const PostgisInflectionPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,52 @@
1
+ import { GisSubtype, SUBTYPE_STRING_BY_SUBTYPE } from '../constants';
2
+ // Import types.ts for the Inflection augmentation side effects
3
+ import '../types';
4
+ /**
5
+ * PostgisInflectionPlugin
6
+ *
7
+ * Adds inflection methods for generating PostGIS-related GraphQL type names.
8
+ */
9
+ export const PostgisInflectionPlugin = {
10
+ name: 'PostgisInflectionPlugin',
11
+ version: '2.0.0',
12
+ description: 'Adds PostGIS-related inflection methods',
13
+ inflection: {
14
+ add: {
15
+ gisType(_options, typeName, subtype, hasZ, hasM, _srid) {
16
+ return this.upperCamelCase([
17
+ typeName,
18
+ SUBTYPE_STRING_BY_SUBTYPE[subtype],
19
+ hasZ ? 'z' : null,
20
+ hasM ? 'm' : null
21
+ ]
22
+ .filter(Boolean)
23
+ .join('-'));
24
+ },
25
+ gisInterfaceName(_options, typeName) {
26
+ return this.upperCamelCase(`${typeName}-interface`);
27
+ },
28
+ gisDimensionInterfaceName(_options, typeName, hasZ, hasM) {
29
+ return this.upperCamelCase([
30
+ typeName,
31
+ SUBTYPE_STRING_BY_SUBTYPE[GisSubtype.Geometry],
32
+ hasZ ? 'z' : null,
33
+ hasM ? 'm' : null
34
+ ]
35
+ .filter(Boolean)
36
+ .join('-'));
37
+ },
38
+ geojsonFieldName() {
39
+ return 'geojson';
40
+ },
41
+ gisXFieldName(_options, typeName) {
42
+ return typeName === 'geography' ? 'longitude' : 'x';
43
+ },
44
+ gisYFieldName(_options, typeName) {
45
+ return typeName === 'geography' ? 'latitude' : 'y';
46
+ },
47
+ gisZFieldName(_options, typeName) {
48
+ return typeName === 'geography' ? 'height' : 'z';
49
+ }
50
+ }
51
+ }
52
+ };
@@ -0,0 +1,22 @@
1
+ import 'graphile-build';
2
+ import 'graphile-build-pg';
3
+ import type { GraphileConfig } from 'graphile-config';
4
+ import '../types';
5
+ /**
6
+ * PostgisRegisterTypesPlugin
7
+ *
8
+ * The core plugin that:
9
+ * 1. Registers a GeoJSON scalar type
10
+ * 2. Creates GraphQL interfaces for geometry/geography base types
11
+ * 3. Creates GraphQL interfaces for each dimension combination (XY, XYZ, XYM, XYZM)
12
+ * 4. Creates concrete GraphQL object types for each subtype/dimension combo
13
+ * 5. Registers codec-to-type mappings so PostGraphile knows how to handle
14
+ * geometry/geography columns
15
+ *
16
+ * In v5, type registration is done during the init hook using
17
+ * build.registerObjectType / build.registerInterfaceType / build.registerScalarType.
18
+ *
19
+ * The SQL tweak wraps geometry/geography values in json_build_object() containing
20
+ * __gisType, __srid, and __geojson fields, which downstream resolvers use.
21
+ */
22
+ export declare const PostgisRegisterTypesPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,319 @@
1
+ import 'graphile-build';
2
+ import 'graphile-build-pg';
3
+ import sql from 'pg-sql2';
4
+ import { GisSubtype, CONCRETE_SUBTYPES } from '../constants';
5
+ import { getGISTypeDetails, getGISTypeModifier, getGISTypeName } from '../utils';
6
+ // Import types.ts for the Build/Inflection/Scope augmentation side effects
7
+ import '../types';
8
+ /**
9
+ * PostgisRegisterTypesPlugin
10
+ *
11
+ * The core plugin that:
12
+ * 1. Registers a GeoJSON scalar type
13
+ * 2. Creates GraphQL interfaces for geometry/geography base types
14
+ * 3. Creates GraphQL interfaces for each dimension combination (XY, XYZ, XYM, XYZM)
15
+ * 4. Creates concrete GraphQL object types for each subtype/dimension combo
16
+ * 5. Registers codec-to-type mappings so PostGraphile knows how to handle
17
+ * geometry/geography columns
18
+ *
19
+ * In v5, type registration is done during the init hook using
20
+ * build.registerObjectType / build.registerInterfaceType / build.registerScalarType.
21
+ *
22
+ * The SQL tweak wraps geometry/geography values in json_build_object() containing
23
+ * __gisType, __srid, and __geojson fields, which downstream resolvers use.
24
+ */
25
+ export const PostgisRegisterTypesPlugin = {
26
+ name: 'PostgisRegisterTypesPlugin',
27
+ version: '2.0.0',
28
+ description: 'Registers PostGIS GeoJSON scalar and geometry/geography types',
29
+ after: ['PostgisExtensionDetectionPlugin', 'PostgisInflectionPlugin'],
30
+ schema: {
31
+ hooks: {
32
+ init(_, build) {
33
+ const postgisInfo = build.pgGISExtensionInfo;
34
+ if (!postgisInfo) {
35
+ return _;
36
+ }
37
+ const { inflection, graphql: { GraphQLInt, GraphQLNonNull, Kind } } = build;
38
+ const constructedTypes = build.pgGISGraphQLTypesByCodecAndSubtype;
39
+ if (!constructedTypes) {
40
+ return _;
41
+ }
42
+ const { geometryCodec, geographyCodec } = postgisInfo;
43
+ // Register the GeoJSON scalar type
44
+ build.registerScalarType('GeoJSON', {}, () => ({
45
+ description: 'The `GeoJSON` scalar type represents GeoJSON values as specified by ' +
46
+ '[RFC 7946](https://tools.ietf.org/html/rfc7946).',
47
+ serialize: (value) => value,
48
+ parseValue: (value) => {
49
+ if (value === null || value === undefined)
50
+ return value;
51
+ if (typeof value !== 'object' || Array.isArray(value)) {
52
+ throw new TypeError('GeoJSON must be an object');
53
+ }
54
+ const obj = value;
55
+ if (typeof obj.type !== 'string') {
56
+ throw new TypeError('GeoJSON must have a "type" string property');
57
+ }
58
+ if (obj.type === 'Feature' || obj.type === 'FeatureCollection') {
59
+ throw new TypeError(`GeoJSON type "${obj.type}" is not supported for PostGIS geometry input. Extract the geometry from your Feature first.`);
60
+ }
61
+ const validTypes = ['Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'];
62
+ if (!validTypes.includes(obj.type)) {
63
+ throw new TypeError(`GeoJSON type "${obj.type}" is not a recognized GeoJSON type`);
64
+ }
65
+ return value;
66
+ },
67
+ parseLiteral(ast, variables) {
68
+ return parseLiteralGeoJSON(ast, variables, Kind);
69
+ }
70
+ }), 'PostgisRegisterTypesPlugin registering GeoJSON scalar');
71
+ // Process geometry and (optionally) geography types
72
+ const gisCodecs = [geometryCodec, geographyCodec].filter((c) => c !== null);
73
+ for (const gisCodec of gisCodecs) {
74
+ const key = gisCodec.name;
75
+ const typeName = gisCodec.name;
76
+ if (!constructedTypes[key]) {
77
+ constructedTypes[key] = {};
78
+ }
79
+ // Register the main interface (no dimensional constraint)
80
+ const mainInterfaceName = inflection.gisInterfaceName(typeName);
81
+ build.registerInterfaceType(mainInterfaceName, {
82
+ isPgGISInterface: true,
83
+ pgGISCodecName: typeName,
84
+ pgGISZMFlag: -1
85
+ }, () => ({
86
+ description: `All ${typeName} types implement this interface`,
87
+ fields: () => {
88
+ const geoJsonType = build.getTypeByName('GeoJSON');
89
+ if (!geoJsonType) {
90
+ throw new Error('PostGIS: GeoJSON scalar type not found.');
91
+ }
92
+ return {
93
+ [inflection.geojsonFieldName()]: {
94
+ type: geoJsonType,
95
+ description: 'Converts the object to GeoJSON'
96
+ },
97
+ srid: {
98
+ type: new GraphQLNonNull(GraphQLInt),
99
+ description: 'Spatial reference identifier (SRID)'
100
+ }
101
+ };
102
+ },
103
+ resolveType(value) {
104
+ const gisTypeKey = value.__gisType;
105
+ const resolvedTypeName = constructedTypes[key]?.[gisTypeKey];
106
+ if (typeof resolvedTypeName === 'string') {
107
+ return resolvedTypeName;
108
+ }
109
+ throw new Error(`PostGIS: Could not resolve type for __gisType="${gisTypeKey}" on codec="${key}". Known types: ${Object.keys(constructedTypes[key] ?? {}).join(', ')}`);
110
+ }
111
+ }), `PostgisRegisterTypesPlugin registering ${mainInterfaceName} interface`);
112
+ // Register dimension interfaces (XY, XYZ, XYM, XYZM)
113
+ for (const hasZ of [false, true]) {
114
+ for (const hasM of [false, true]) {
115
+ const zmflag = (hasZ ? 2 : 0) + (hasM ? 1 : 0);
116
+ const coords = { 0: 'XY', 1: 'XYM', 2: 'XYZ', 3: 'XYZM' };
117
+ const dimInterfaceName = inflection.gisDimensionInterfaceName(typeName, hasZ, hasM);
118
+ build.registerInterfaceType(dimInterfaceName, {
119
+ isPgGISDimensionInterface: true,
120
+ pgGISCodecName: typeName,
121
+ pgGISZMFlag: zmflag
122
+ }, () => ({
123
+ description: `All ${typeName} ${coords[zmflag]} types implement this interface`,
124
+ fields: () => {
125
+ const geoJsonType = build.getTypeByName('GeoJSON');
126
+ if (!geoJsonType) {
127
+ throw new Error('PostGIS: GeoJSON scalar type not found.');
128
+ }
129
+ return {
130
+ [inflection.geojsonFieldName()]: {
131
+ type: geoJsonType,
132
+ description: 'Converts the object to GeoJSON'
133
+ },
134
+ srid: {
135
+ type: new GraphQLNonNull(GraphQLInt),
136
+ description: 'Spatial reference identifier (SRID)'
137
+ }
138
+ };
139
+ },
140
+ resolveType(value) {
141
+ const gisTypeKey = value.__gisType;
142
+ const concreteTypeName = constructedTypes[key]?.[gisTypeKey];
143
+ if (typeof concreteTypeName === 'string') {
144
+ return concreteTypeName;
145
+ }
146
+ throw new Error(`PostGIS: Could not resolve type for __gisType="${gisTypeKey}" on codec="${key}". Known types: ${Object.keys(constructedTypes[key] ?? {}).join(', ')}`);
147
+ }
148
+ }), `PostgisRegisterTypesPlugin registering ${dimInterfaceName} interface`);
149
+ // Register concrete object types for each subtype + this dimension
150
+ for (const subtype of CONCRETE_SUBTYPES) {
151
+ const concreteTypeName = inflection.gisType(typeName, subtype, hasZ, hasM, 0);
152
+ const typeModifier = getGISTypeModifier(subtype, hasZ, hasM, 0);
153
+ const typeDetails = getGISTypeDetails(typeModifier);
154
+ build.registerObjectType(concreteTypeName, {
155
+ isPgGISType: true,
156
+ pgGISCodecName: typeName,
157
+ pgGISTypeDetails: typeDetails
158
+ }, () => ({
159
+ description: `A PostGIS ${typeName} ${getGISTypeName(subtype, hasZ, hasM)} type`,
160
+ interfaces: () => [
161
+ build.getTypeByName(mainInterfaceName),
162
+ build.getTypeByName(dimInterfaceName)
163
+ ].filter(Boolean),
164
+ fields: () => {
165
+ const geoJsonType = build.getTypeByName('GeoJSON');
166
+ if (!geoJsonType) {
167
+ throw new Error('PostGIS: GeoJSON scalar type not found.');
168
+ }
169
+ return {
170
+ [inflection.geojsonFieldName()]: {
171
+ type: geoJsonType,
172
+ description: 'Converts the object to GeoJSON',
173
+ resolve(data) {
174
+ return data.__geojson;
175
+ }
176
+ },
177
+ srid: {
178
+ type: new GraphQLNonNull(GraphQLInt),
179
+ description: 'Spatial reference identifier (SRID)',
180
+ resolve(data) {
181
+ return data.__srid;
182
+ }
183
+ }
184
+ };
185
+ }
186
+ }), `PostgisRegisterTypesPlugin registering ${concreteTypeName} type`);
187
+ // Track the type by its gisTypeKey for resolveType lookups
188
+ const gisTypeKey = getGISTypeName(subtype, hasZ, hasM);
189
+ constructedTypes[key][gisTypeKey] = concreteTypeName;
190
+ }
191
+ // Map the Geometry subtype (0) for this dimension to the dimension interface
192
+ const geomDimKey = getGISTypeName(GisSubtype.Geometry, hasZ, hasM);
193
+ constructedTypes[key][geomDimKey] = dimInterfaceName;
194
+ }
195
+ }
196
+ // Map null/unspecified modifier to the main interface
197
+ constructedTypes[key][-1] = mainInterfaceName;
198
+ }
199
+ // Register type mappings so PostGraphile knows what GraphQL types to
200
+ // use for geometry/geography columns.
201
+ //
202
+ // Without BOTH input AND output mappings, PgAttributesPlugin silently
203
+ // omits geometry columns from the schema:
204
+ // - Input: GeoJSON scalar (accepts GeoJSON objects)
205
+ // - Output: The main interface type (e.g. GeometryInterface), which uses
206
+ // resolveType to dispatch to concrete types (GeometryPoint, etc.)
207
+ const { setGraphQLTypeForPgCodec } = build;
208
+ if (typeof setGraphQLTypeForPgCodec === 'function') {
209
+ for (const gisCodec of gisCodecs) {
210
+ const mainInterfaceName = inflection.gisInterfaceName(gisCodec.name);
211
+ setGraphQLTypeForPgCodec(gisCodec, 'input', 'GeoJSON');
212
+ setGraphQLTypeForPgCodec(gisCodec, 'output', mainInterfaceName);
213
+ }
214
+ }
215
+ return _;
216
+ },
217
+ build(build) {
218
+ const postgisInfo = build.pgGISExtensionInfo;
219
+ if (!postgisInfo) {
220
+ return build;
221
+ }
222
+ const { schemaName } = postgisInfo;
223
+ const constructedTypes = build.pgGISGraphQLTypesByCodecAndSubtype;
224
+ if (!constructedTypes) {
225
+ return build;
226
+ }
227
+ return build.extend(build, {
228
+ getPostgisTypeByGeometryType(gisCodecName, subtype, hasZ = false, hasM = false, _srid = 0) {
229
+ const gisTypeKey = getGISTypeName(subtype, hasZ, hasM);
230
+ const resolvedTypeName = constructedTypes[gisCodecName]?.[gisTypeKey];
231
+ if (typeof resolvedTypeName === 'string') {
232
+ return build.getTypeByName(resolvedTypeName);
233
+ }
234
+ return undefined;
235
+ },
236
+ pgGISWrapExpression(fragment) {
237
+ // PostGIS function names MUST be lowercase for PostgreSQL identifier matching
238
+ const params = [
239
+ sql.literal('__gisType'),
240
+ sql.fragment `${sql.identifier(schemaName, 'geometrytype')}(${fragment})`,
241
+ sql.literal('__srid'),
242
+ sql.fragment `${sql.identifier(schemaName, 'st_srid')}(${fragment})`,
243
+ sql.literal('__geojson'),
244
+ sql.fragment `${sql.identifier(schemaName, 'st_asgeojson')}(${fragment})::JSON`
245
+ ];
246
+ return sql.fragment `(case when ${fragment} is null then null else json_build_object(
247
+ ${sql.join(params, ', ')}
248
+ ) end)`;
249
+ },
250
+ pgGISFromGeoJSON(value, codecName) {
251
+ const jsonStr = sql.value(JSON.stringify(value));
252
+ if (codecName === 'geography') {
253
+ return sql.fragment `${sql.identifier(schemaName, 'st_geomfromgeojson')}(${jsonStr}::text)::${sql.identifier(schemaName, 'geography')}`;
254
+ }
255
+ return sql.fragment `${sql.identifier(schemaName, 'st_geomfromgeojson')}(${jsonStr}::text)`;
256
+ }
257
+ }, 'PostgisRegisterTypesPlugin adding PostGIS helpers to build');
258
+ },
259
+ // Add all registered PostGIS concrete types to the schema so they are
260
+ // discoverable for resolveType on the interfaces. Types registered via
261
+ // build.registerObjectType() during init are lazy — they are only
262
+ // materialized when getTypeByName() is called.
263
+ //
264
+ // We MUST use GraphQLSchema_types (not GraphQLSchema) because the
265
+ // GraphQLSchema hook's types property gets overwritten by the
266
+ // GraphQLSchema_types hook that runs immediately after it.
267
+ GraphQLSchema_types(types, build) {
268
+ const constructedTypes = build.pgGISGraphQLTypesByCodecAndSubtype;
269
+ if (!constructedTypes) {
270
+ return types;
271
+ }
272
+ for (const codecTypes of Object.values(constructedTypes)) {
273
+ for (const typeName of Object.values(codecTypes)) {
274
+ if (typeof typeName === 'string') {
275
+ const graphqlType = build.getTypeByName(typeName);
276
+ if (graphqlType && !types.includes(graphqlType)) {
277
+ types.push(graphqlType);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ return types;
283
+ }
284
+ }
285
+ }
286
+ };
287
+ /**
288
+ * Recursively parses a GeoJSON literal from a GraphQL AST.
289
+ */
290
+ function parseLiteralGeoJSON(ast, variables, Kind, depth = 0) {
291
+ if (depth > 32) {
292
+ throw new Error('GeoJSON input exceeds maximum nesting depth');
293
+ }
294
+ switch (ast.kind) {
295
+ case Kind.STRING:
296
+ case Kind.BOOLEAN:
297
+ return ast.value;
298
+ case Kind.INT:
299
+ case Kind.FLOAT:
300
+ return parseFloat(ast.value);
301
+ case Kind.OBJECT: {
302
+ const value = Object.create(null);
303
+ ast.fields.forEach((field) => {
304
+ value[field.name.value] = parseLiteralGeoJSON(field.value, variables, Kind, depth + 1);
305
+ });
306
+ return value;
307
+ }
308
+ case Kind.LIST:
309
+ return ast.values.map((n) => parseLiteralGeoJSON(n, variables, Kind, depth + 1));
310
+ case Kind.NULL:
311
+ return null;
312
+ case Kind.VARIABLE: {
313
+ const variableName = ast.name.value;
314
+ return variables ? variables[variableName] : undefined;
315
+ }
316
+ default:
317
+ return undefined;
318
+ }
319
+ }
@@ -0,0 +1,18 @@
1
+ import type { GraphileConfig } from 'graphile-config';
2
+ /**
3
+ * GraphilePostgisPreset
4
+ *
5
+ * A preset that includes all PostGIS plugins for PostGraphile v5.
6
+ * Use this as the recommended way to add PostGIS support.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { GraphilePostgisPreset } from 'graphile-postgis';
11
+ *
12
+ * const preset = {
13
+ * extends: [GraphilePostgisPreset]
14
+ * };
15
+ * ```
16
+ */
17
+ export declare const GraphilePostgisPreset: GraphileConfig.Preset;
18
+ export default GraphilePostgisPreset;