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.
- package/README.md +22 -45
- package/constants.d.ts +1 -0
- package/constants.js +10 -1
- package/esm/constants.d.ts +13 -0
- package/esm/constants.js +9 -0
- package/esm/index.d.ts +24 -0
- package/esm/index.js +25 -33
- package/esm/plugins/codec.d.ts +19 -0
- package/esm/plugins/codec.js +174 -0
- package/esm/plugins/detect-extension.d.ts +14 -0
- package/esm/plugins/detect-extension.js +57 -0
- package/esm/plugins/geometry-fields.d.ts +21 -0
- package/esm/plugins/geometry-fields.js +245 -0
- package/esm/plugins/inflection.d.ts +8 -0
- package/esm/plugins/inflection.js +52 -0
- package/esm/plugins/register-types.d.ts +22 -0
- package/esm/plugins/register-types.js +319 -0
- package/esm/preset.d.ts +18 -0
- package/esm/preset.js +30 -0
- package/esm/types.d.ts +84 -0
- package/esm/utils.d.ts +21 -0
- package/esm/utils.js +18 -7
- package/index.d.ts +24 -15
- package/index.js +39 -47
- package/package.json +23 -18
- package/plugins/codec.d.ts +19 -0
- package/plugins/codec.js +180 -0
- package/plugins/detect-extension.d.ts +14 -0
- package/plugins/detect-extension.js +60 -0
- package/plugins/geometry-fields.d.ts +21 -0
- package/plugins/geometry-fields.js +248 -0
- package/plugins/inflection.d.ts +8 -0
- package/plugins/inflection.js +55 -0
- package/plugins/register-types.d.ts +22 -0
- package/plugins/register-types.js +325 -0
- package/preset.d.ts +18 -0
- package/preset.js +33 -0
- package/types.d.ts +69 -44
- package/utils.d.ts +16 -0
- package/utils.js +17 -6
- package/PostgisExtensionDetectionPlugin.d.ts +0 -3
- package/PostgisExtensionDetectionPlugin.js +0 -28
- package/PostgisInflectionPlugin.d.ts +0 -3
- package/PostgisInflectionPlugin.js +0 -36
- package/PostgisRegisterTypesPlugin.d.ts +0 -3
- package/PostgisRegisterTypesPlugin.js +0 -234
- package/PostgisVersionPlugin.d.ts +0 -3
- package/PostgisVersionPlugin.js +0 -24
- package/Postgis_GeometryCollection_GeometriesPlugin.d.ts +0 -3
- package/Postgis_GeometryCollection_GeometriesPlugin.js +0 -43
- package/Postgis_LineString_PointsPlugin.d.ts +0 -3
- package/Postgis_LineString_PointsPlugin.js +0 -40
- package/Postgis_MultiLineString_LineStringsPlugin.d.ts +0 -3
- package/Postgis_MultiLineString_LineStringsPlugin.js +0 -38
- package/Postgis_MultiPoint_PointsPlugin.d.ts +0 -3
- package/Postgis_MultiPoint_PointsPlugin.js +0 -38
- package/Postgis_MultiPolygon_PolygonsPlugin.d.ts +0 -3
- package/Postgis_MultiPolygon_PolygonsPlugin.js +0 -38
- package/Postgis_Point_LatitudeLongitudePlugin.d.ts +0 -3
- package/Postgis_Point_LatitudeLongitudePlugin.js +0 -43
- package/Postgis_Polygon_RingsPlugin.d.ts +0 -3
- package/Postgis_Polygon_RingsPlugin.js +0 -49
- package/esm/PostgisExtensionDetectionPlugin.js +0 -26
- package/esm/PostgisInflectionPlugin.js +0 -34
- package/esm/PostgisRegisterTypesPlugin.js +0 -229
- package/esm/PostgisVersionPlugin.js +0 -22
- package/esm/Postgis_GeometryCollection_GeometriesPlugin.js +0 -41
- package/esm/Postgis_LineString_PointsPlugin.js +0 -38
- package/esm/Postgis_MultiLineString_LineStringsPlugin.js +0 -36
- package/esm/Postgis_MultiPoint_PointsPlugin.js +0 -36
- package/esm/Postgis_MultiPolygon_PolygonsPlugin.js +0 -36
- package/esm/Postgis_Point_LatitudeLongitudePlugin.js +0 -41
- package/esm/Postgis_Polygon_RingsPlugin.js +0 -47
- package/esm/makeGeoJSONType.js +0 -39
- package/makeGeoJSONType.d.ts +0 -1
- 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
|
+
}
|
package/esm/preset.d.ts
ADDED
|
@@ -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;
|