graphile-postgis 2.5.2 → 2.6.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/esm/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { PostgisInflectionPlugin } from './plugins/inflection';
18
18
  export { PostgisExtensionDetectionPlugin } from './plugins/detect-extension';
19
19
  export { PostgisRegisterTypesPlugin } from './plugins/register-types';
20
20
  export { PostgisGeometryFieldsPlugin } from './plugins/geometry-fields';
21
+ export { createPostgisOperatorFactory } from './plugins/connection-filter-operators';
21
22
  export { GisSubtype, SUBTYPE_STRING_BY_SUBTYPE, GIS_SUBTYPE_NAME, CONCRETE_SUBTYPES } from './constants';
22
23
  export { getGISTypeDetails, getGISTypeModifier, getGISTypeName } from './utils';
23
24
  export type { GisTypeDetails, GisFieldValue } from './types';
package/esm/index.js CHANGED
@@ -20,6 +20,8 @@ export { PostgisInflectionPlugin } from './plugins/inflection';
20
20
  export { PostgisExtensionDetectionPlugin } from './plugins/detect-extension';
21
21
  export { PostgisRegisterTypesPlugin } from './plugins/register-types';
22
22
  export { PostgisGeometryFieldsPlugin } from './plugins/geometry-fields';
23
+ // Connection filter operator factory (spatial operators for graphile-connection-filter)
24
+ export { createPostgisOperatorFactory } from './plugins/connection-filter-operators';
23
25
  // Constants and utilities
24
26
  export { GisSubtype, SUBTYPE_STRING_BY_SUBTYPE, GIS_SUBTYPE_NAME, CONCRETE_SUBTYPES } from './constants';
25
27
  export { getGISTypeDetails, getGISTypeModifier, getGISTypeName } from './utils';
@@ -0,0 +1,15 @@
1
+ import 'graphile-build';
2
+ import 'graphile-connection-filter';
3
+ import type { ConnectionFilterOperatorFactory } from 'graphile-connection-filter';
4
+ /**
5
+ * Creates the PostGIS spatial filter operator factory.
6
+ *
7
+ * This factory dynamically generates operator registrations based on the
8
+ * PostGIS extension info discovered during the build phase. It discovers
9
+ * all geometry/geography GQL type names and creates ST_ function-based
10
+ * and SQL operator-based filter operators for each.
11
+ *
12
+ * Registered via the declarative `connectionFilterOperatorFactories` API
13
+ * in the GraphilePostgisPreset.
14
+ */
15
+ export declare function createPostgisOperatorFactory(): ConnectionFilterOperatorFactory;
@@ -0,0 +1,271 @@
1
+ import 'graphile-build';
2
+ import 'graphile-connection-filter';
3
+ import sql from 'pg-sql2';
4
+ import { CONCRETE_SUBTYPES } from '../constants';
5
+ const ALLOWED_SQL_OPERATORS = new Set([
6
+ '=',
7
+ '&&',
8
+ '&&&',
9
+ '&<',
10
+ '&<|',
11
+ '&>',
12
+ '|&>',
13
+ '<<',
14
+ '<<|',
15
+ '>>',
16
+ '|>>',
17
+ '~',
18
+ '~=',
19
+ ]);
20
+ // PostGIS function-based operators
21
+ const FUNCTION_SPECS = [
22
+ [
23
+ 'ST_3DIntersects',
24
+ ['geometry'],
25
+ 'intersects3D',
26
+ 'They share any portion of space in 3D.'
27
+ ],
28
+ [
29
+ 'ST_Contains',
30
+ ['geometry'],
31
+ 'contains',
32
+ 'No points of the specified geometry lie in the exterior, and at least one point of the interior of the specified geometry lies in the interior.'
33
+ ],
34
+ [
35
+ 'ST_ContainsProperly',
36
+ ['geometry'],
37
+ 'containsProperly',
38
+ 'The specified geometry intersects the interior but not the boundary (or exterior).'
39
+ ],
40
+ [
41
+ 'ST_CoveredBy',
42
+ ['geometry', 'geography'],
43
+ 'coveredBy',
44
+ 'No point is outside the specified geometry.'
45
+ ],
46
+ [
47
+ 'ST_Covers',
48
+ ['geometry', 'geography'],
49
+ 'covers',
50
+ 'No point in the specified geometry is outside.'
51
+ ],
52
+ [
53
+ 'ST_Crosses',
54
+ ['geometry'],
55
+ 'crosses',
56
+ 'They have some, but not all, interior points in common.'
57
+ ],
58
+ [
59
+ 'ST_Disjoint',
60
+ ['geometry'],
61
+ 'disjoint',
62
+ 'They do not share any space together.'
63
+ ],
64
+ [
65
+ 'ST_Equals',
66
+ ['geometry'],
67
+ 'equals',
68
+ 'They represent the same geometry. Directionality is ignored.'
69
+ ],
70
+ [
71
+ 'ST_Intersects',
72
+ ['geometry', 'geography'],
73
+ 'intersects',
74
+ 'They share any portion of space in 2D.'
75
+ ],
76
+ [
77
+ 'ST_OrderingEquals',
78
+ ['geometry'],
79
+ 'orderingEquals',
80
+ 'They represent the same geometry and points are in the same directional order.'
81
+ ],
82
+ [
83
+ 'ST_Overlaps',
84
+ ['geometry'],
85
+ 'overlaps',
86
+ 'They share space, are of the same dimension, but are not completely contained by each other.'
87
+ ],
88
+ [
89
+ 'ST_Touches',
90
+ ['geometry'],
91
+ 'touches',
92
+ 'They have at least one point in common, but their interiors do not intersect.'
93
+ ],
94
+ [
95
+ 'ST_Within',
96
+ ['geometry'],
97
+ 'within',
98
+ 'Completely inside the specified geometry.'
99
+ ]
100
+ ];
101
+ // SQL operator-based operators
102
+ const OPERATOR_SPECS = [
103
+ [
104
+ '=',
105
+ ['geometry', 'geography'],
106
+ 'exactlyEquals',
107
+ 'Coordinates and coordinate order are the same as specified geometry.'
108
+ ],
109
+ [
110
+ '&&',
111
+ ['geometry', 'geography'],
112
+ 'bboxIntersects2D',
113
+ "2D bounding box intersects the specified geometry's 2D bounding box."
114
+ ],
115
+ [
116
+ '&&&',
117
+ ['geometry'],
118
+ 'bboxIntersectsND',
119
+ "n-D bounding box intersects the specified geometry's n-D bounding box."
120
+ ],
121
+ [
122
+ '&<',
123
+ ['geometry'],
124
+ 'bboxOverlapsOrLeftOf',
125
+ "Bounding box overlaps or is to the left of the specified geometry's bounding box."
126
+ ],
127
+ [
128
+ '&<|',
129
+ ['geometry'],
130
+ 'bboxOverlapsOrBelow',
131
+ "Bounding box overlaps or is below the specified geometry's bounding box."
132
+ ],
133
+ [
134
+ '&>',
135
+ ['geometry'],
136
+ 'bboxOverlapsOrRightOf',
137
+ "Bounding box overlaps or is to the right of the specified geometry's bounding box."
138
+ ],
139
+ [
140
+ '|&>',
141
+ ['geometry'],
142
+ 'bboxOverlapsOrAbove',
143
+ "Bounding box overlaps or is above the specified geometry's bounding box."
144
+ ],
145
+ [
146
+ '<<',
147
+ ['geometry'],
148
+ 'bboxLeftOf',
149
+ "Bounding box is strictly to the left of the specified geometry's bounding box."
150
+ ],
151
+ [
152
+ '<<|',
153
+ ['geometry'],
154
+ 'bboxBelow',
155
+ "Bounding box is strictly below the specified geometry's bounding box."
156
+ ],
157
+ [
158
+ '>>',
159
+ ['geometry'],
160
+ 'bboxRightOf',
161
+ "Bounding box is strictly to the right of the specified geometry's bounding box."
162
+ ],
163
+ [
164
+ '|>>',
165
+ ['geometry'],
166
+ 'bboxAbove',
167
+ "Bounding box is strictly above the specified geometry's bounding box."
168
+ ],
169
+ [
170
+ '~',
171
+ ['geometry'],
172
+ 'bboxContains',
173
+ "Bounding box contains the specified geometry's bounding box."
174
+ ],
175
+ [
176
+ '~=',
177
+ ['geometry'],
178
+ 'bboxEquals',
179
+ "Bounding box is the same as the specified geometry's bounding box."
180
+ ]
181
+ ];
182
+ /**
183
+ * Creates the PostGIS spatial filter operator factory.
184
+ *
185
+ * This factory dynamically generates operator registrations based on the
186
+ * PostGIS extension info discovered during the build phase. It discovers
187
+ * all geometry/geography GQL type names and creates ST_ function-based
188
+ * and SQL operator-based filter operators for each.
189
+ *
190
+ * Registered via the declarative `connectionFilterOperatorFactories` API
191
+ * in the GraphilePostgisPreset.
192
+ */
193
+ export function createPostgisOperatorFactory() {
194
+ return (build) => {
195
+ const postgisInfo = build.pgGISExtensionInfo;
196
+ if (!postgisInfo) {
197
+ return [];
198
+ }
199
+ const { inflection } = build;
200
+ const { schemaName, geometryCodec, geographyCodec } = postgisInfo;
201
+ // Collect all GQL type names for geometry and geography
202
+ const gqlTypeNamesByBase = {
203
+ geometry: [],
204
+ geography: []
205
+ };
206
+ const codecPairs = [['geometry', geometryCodec]];
207
+ if (geographyCodec) {
208
+ codecPairs.push(['geography', geographyCodec]);
209
+ }
210
+ for (const [baseKey, codec] of codecPairs) {
211
+ const typeName = codec.name;
212
+ gqlTypeNamesByBase[baseKey].push(inflection.gisInterfaceName(typeName));
213
+ for (const subtype of CONCRETE_SUBTYPES) {
214
+ for (const hasZ of [false, true]) {
215
+ for (const hasM of [false, true]) {
216
+ gqlTypeNamesByBase[baseKey].push(inflection.gisType(typeName, subtype, hasZ, hasM, 0));
217
+ }
218
+ }
219
+ }
220
+ }
221
+ const allSpecs = [];
222
+ // Process function-based operators
223
+ for (const [fn, baseTypes, operatorName, description] of FUNCTION_SPECS) {
224
+ for (const baseType of baseTypes) {
225
+ const sqlGisFunction = sql.identifier(schemaName, fn.toLowerCase());
226
+ allSpecs.push({
227
+ typeNames: gqlTypeNamesByBase[baseType],
228
+ operatorName,
229
+ description,
230
+ resolve: (i, v) => sql.fragment `${sqlGisFunction}(${i}, ${v})`
231
+ });
232
+ }
233
+ }
234
+ // Process SQL operator-based operators
235
+ for (const [op, baseTypes, operatorName, description] of OPERATOR_SPECS) {
236
+ if (!ALLOWED_SQL_OPERATORS.has(op)) {
237
+ throw new Error(`Unexpected SQL operator: ${op}`);
238
+ }
239
+ for (const baseType of baseTypes) {
240
+ allSpecs.push({
241
+ typeNames: gqlTypeNamesByBase[baseType],
242
+ operatorName,
243
+ description,
244
+ resolve: (i, v) => sql.fragment `${i} ${sql.raw(op)} ${v}`
245
+ });
246
+ }
247
+ }
248
+ // Sort by operator name for deterministic schema output
249
+ allSpecs.sort((a, b) => a.operatorName.localeCompare(b.operatorName));
250
+ // Convert to ConnectionFilterOperatorRegistration format.
251
+ // Each InternalSpec may target multiple type names; we expand each
252
+ // into individual registrations keyed by typeName.
253
+ const registrations = [];
254
+ for (const spec of allSpecs) {
255
+ for (const typeName of spec.typeNames) {
256
+ registrations.push({
257
+ typeNames: typeName,
258
+ operatorName: spec.operatorName,
259
+ spec: {
260
+ description: spec.description,
261
+ resolveType: (fieldType) => fieldType,
262
+ resolve(sqlIdentifier, sqlValue, _input, _$where, _details) {
263
+ return spec.resolve(sqlIdentifier, sqlValue);
264
+ }
265
+ },
266
+ });
267
+ }
268
+ }
269
+ return registrations;
270
+ };
271
+ }
package/esm/preset.d.ts CHANGED
@@ -5,6 +5,13 @@ import type { GraphileConfig } from 'graphile-config';
5
5
  * A preset that includes all PostGIS plugins for PostGraphile v5.
6
6
  * Use this as the recommended way to add PostGIS support.
7
7
  *
8
+ * Includes:
9
+ * - Geometry/geography type codecs and GeoJSON scalar
10
+ * - PostGIS extension auto-detection
11
+ * - PostGIS inflection (type names for subtypes, Z/M variants)
12
+ * - Geometry field plugins (coordinates, GeoJSON output)
13
+ * - Connection filter operators (26 spatial operators via declarative factory API)
14
+ *
8
15
  * @example
9
16
  * ```typescript
10
17
  * import { GraphilePostgisPreset } from 'graphile-postgis';
package/esm/preset.js CHANGED
@@ -3,12 +3,20 @@ import { PostgisInflectionPlugin } from './plugins/inflection';
3
3
  import { PostgisExtensionDetectionPlugin } from './plugins/detect-extension';
4
4
  import { PostgisRegisterTypesPlugin } from './plugins/register-types';
5
5
  import { PostgisGeometryFieldsPlugin } from './plugins/geometry-fields';
6
+ import { createPostgisOperatorFactory } from './plugins/connection-filter-operators';
6
7
  /**
7
8
  * GraphilePostgisPreset
8
9
  *
9
10
  * A preset that includes all PostGIS plugins for PostGraphile v5.
10
11
  * Use this as the recommended way to add PostGIS support.
11
12
  *
13
+ * Includes:
14
+ * - Geometry/geography type codecs and GeoJSON scalar
15
+ * - PostGIS extension auto-detection
16
+ * - PostGIS inflection (type names for subtypes, Z/M variants)
17
+ * - Geometry field plugins (coordinates, GeoJSON output)
18
+ * - Connection filter operators (26 spatial operators via declarative factory API)
19
+ *
12
20
  * @example
13
21
  * ```typescript
14
22
  * import { GraphilePostgisPreset } from 'graphile-postgis';
@@ -25,6 +33,11 @@ export const GraphilePostgisPreset = {
25
33
  PostgisExtensionDetectionPlugin,
26
34
  PostgisRegisterTypesPlugin,
27
35
  PostgisGeometryFieldsPlugin
28
- ]
36
+ ],
37
+ schema: {
38
+ connectionFilterOperatorFactories: [
39
+ createPostgisOperatorFactory(),
40
+ ],
41
+ },
29
42
  };
30
43
  export default GraphilePostgisPreset;
package/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { PostgisInflectionPlugin } from './plugins/inflection';
18
18
  export { PostgisExtensionDetectionPlugin } from './plugins/detect-extension';
19
19
  export { PostgisRegisterTypesPlugin } from './plugins/register-types';
20
20
  export { PostgisGeometryFieldsPlugin } from './plugins/geometry-fields';
21
+ export { createPostgisOperatorFactory } from './plugins/connection-filter-operators';
21
22
  export { GisSubtype, SUBTYPE_STRING_BY_SUBTYPE, GIS_SUBTYPE_NAME, CONCRETE_SUBTYPES } from './constants';
22
23
  export { getGISTypeDetails, getGISTypeModifier, getGISTypeName } from './utils';
23
24
  export type { GisTypeDetails, GisFieldValue } from './types';
package/index.js CHANGED
@@ -14,7 +14,7 @@
14
14
  * ```
15
15
  */
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.getGISTypeName = exports.getGISTypeModifier = exports.getGISTypeDetails = exports.CONCRETE_SUBTYPES = exports.GIS_SUBTYPE_NAME = exports.SUBTYPE_STRING_BY_SUBTYPE = exports.GisSubtype = exports.PostgisGeometryFieldsPlugin = exports.PostgisRegisterTypesPlugin = exports.PostgisExtensionDetectionPlugin = exports.PostgisInflectionPlugin = exports.PostgisCodecPlugin = exports.GraphilePostgisPreset = void 0;
17
+ exports.getGISTypeName = exports.getGISTypeModifier = exports.getGISTypeDetails = exports.CONCRETE_SUBTYPES = exports.GIS_SUBTYPE_NAME = exports.SUBTYPE_STRING_BY_SUBTYPE = exports.GisSubtype = exports.createPostgisOperatorFactory = exports.PostgisGeometryFieldsPlugin = exports.PostgisRegisterTypesPlugin = exports.PostgisExtensionDetectionPlugin = exports.PostgisInflectionPlugin = exports.PostgisCodecPlugin = exports.GraphilePostgisPreset = void 0;
18
18
  // Preset (recommended entry point)
19
19
  var preset_1 = require("./preset");
20
20
  Object.defineProperty(exports, "GraphilePostgisPreset", { enumerable: true, get: function () { return preset_1.GraphilePostgisPreset; } });
@@ -29,6 +29,9 @@ var register_types_1 = require("./plugins/register-types");
29
29
  Object.defineProperty(exports, "PostgisRegisterTypesPlugin", { enumerable: true, get: function () { return register_types_1.PostgisRegisterTypesPlugin; } });
30
30
  var geometry_fields_1 = require("./plugins/geometry-fields");
31
31
  Object.defineProperty(exports, "PostgisGeometryFieldsPlugin", { enumerable: true, get: function () { return geometry_fields_1.PostgisGeometryFieldsPlugin; } });
32
+ // Connection filter operator factory (spatial operators for graphile-connection-filter)
33
+ var connection_filter_operators_1 = require("./plugins/connection-filter-operators");
34
+ Object.defineProperty(exports, "createPostgisOperatorFactory", { enumerable: true, get: function () { return connection_filter_operators_1.createPostgisOperatorFactory; } });
32
35
  // Constants and utilities
33
36
  var constants_1 = require("./constants");
34
37
  Object.defineProperty(exports, "GisSubtype", { enumerable: true, get: function () { return constants_1.GisSubtype; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-postgis",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
4
4
  "description": "PostGIS support for PostGraphile v5",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/constructive",
@@ -46,10 +46,16 @@
46
46
  "graphile-build": "5.0.0-rc.4",
47
47
  "graphile-build-pg": "5.0.0-rc.5",
48
48
  "graphile-config": "1.0.0-rc.5",
49
+ "graphile-connection-filter": "^1.1.0",
49
50
  "graphql": "^16.9.0",
50
51
  "pg-sql2": "5.0.0-rc.4",
51
52
  "postgraphile": "5.0.0-rc.7"
52
53
  },
54
+ "peerDependenciesMeta": {
55
+ "graphile-connection-filter": {
56
+ "optional": true
57
+ }
58
+ },
53
59
  "devDependencies": {
54
60
  "@types/geojson": "^7946.0.14",
55
61
  "@types/node": "^22.19.11",
@@ -57,5 +63,5 @@
57
63
  "makage": "^0.1.10",
58
64
  "pgsql-test": "^4.5.2"
59
65
  },
60
- "gitHead": "4fd2c9be786ad9ae2213453276a69723435d5315"
66
+ "gitHead": "1ade5f10df8e38a5f87bbd2e2f7ec2ba97267079"
61
67
  }
@@ -0,0 +1,15 @@
1
+ import 'graphile-build';
2
+ import 'graphile-connection-filter';
3
+ import type { ConnectionFilterOperatorFactory } from 'graphile-connection-filter';
4
+ /**
5
+ * Creates the PostGIS spatial filter operator factory.
6
+ *
7
+ * This factory dynamically generates operator registrations based on the
8
+ * PostGIS extension info discovered during the build phase. It discovers
9
+ * all geometry/geography GQL type names and creates ST_ function-based
10
+ * and SQL operator-based filter operators for each.
11
+ *
12
+ * Registered via the declarative `connectionFilterOperatorFactories` API
13
+ * in the GraphilePostgisPreset.
14
+ */
15
+ export declare function createPostgisOperatorFactory(): ConnectionFilterOperatorFactory;
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createPostgisOperatorFactory = createPostgisOperatorFactory;
7
+ require("graphile-build");
8
+ require("graphile-connection-filter");
9
+ const pg_sql2_1 = __importDefault(require("pg-sql2"));
10
+ const constants_1 = require("../constants");
11
+ const ALLOWED_SQL_OPERATORS = new Set([
12
+ '=',
13
+ '&&',
14
+ '&&&',
15
+ '&<',
16
+ '&<|',
17
+ '&>',
18
+ '|&>',
19
+ '<<',
20
+ '<<|',
21
+ '>>',
22
+ '|>>',
23
+ '~',
24
+ '~=',
25
+ ]);
26
+ // PostGIS function-based operators
27
+ const FUNCTION_SPECS = [
28
+ [
29
+ 'ST_3DIntersects',
30
+ ['geometry'],
31
+ 'intersects3D',
32
+ 'They share any portion of space in 3D.'
33
+ ],
34
+ [
35
+ 'ST_Contains',
36
+ ['geometry'],
37
+ 'contains',
38
+ 'No points of the specified geometry lie in the exterior, and at least one point of the interior of the specified geometry lies in the interior.'
39
+ ],
40
+ [
41
+ 'ST_ContainsProperly',
42
+ ['geometry'],
43
+ 'containsProperly',
44
+ 'The specified geometry intersects the interior but not the boundary (or exterior).'
45
+ ],
46
+ [
47
+ 'ST_CoveredBy',
48
+ ['geometry', 'geography'],
49
+ 'coveredBy',
50
+ 'No point is outside the specified geometry.'
51
+ ],
52
+ [
53
+ 'ST_Covers',
54
+ ['geometry', 'geography'],
55
+ 'covers',
56
+ 'No point in the specified geometry is outside.'
57
+ ],
58
+ [
59
+ 'ST_Crosses',
60
+ ['geometry'],
61
+ 'crosses',
62
+ 'They have some, but not all, interior points in common.'
63
+ ],
64
+ [
65
+ 'ST_Disjoint',
66
+ ['geometry'],
67
+ 'disjoint',
68
+ 'They do not share any space together.'
69
+ ],
70
+ [
71
+ 'ST_Equals',
72
+ ['geometry'],
73
+ 'equals',
74
+ 'They represent the same geometry. Directionality is ignored.'
75
+ ],
76
+ [
77
+ 'ST_Intersects',
78
+ ['geometry', 'geography'],
79
+ 'intersects',
80
+ 'They share any portion of space in 2D.'
81
+ ],
82
+ [
83
+ 'ST_OrderingEquals',
84
+ ['geometry'],
85
+ 'orderingEquals',
86
+ 'They represent the same geometry and points are in the same directional order.'
87
+ ],
88
+ [
89
+ 'ST_Overlaps',
90
+ ['geometry'],
91
+ 'overlaps',
92
+ 'They share space, are of the same dimension, but are not completely contained by each other.'
93
+ ],
94
+ [
95
+ 'ST_Touches',
96
+ ['geometry'],
97
+ 'touches',
98
+ 'They have at least one point in common, but their interiors do not intersect.'
99
+ ],
100
+ [
101
+ 'ST_Within',
102
+ ['geometry'],
103
+ 'within',
104
+ 'Completely inside the specified geometry.'
105
+ ]
106
+ ];
107
+ // SQL operator-based operators
108
+ const OPERATOR_SPECS = [
109
+ [
110
+ '=',
111
+ ['geometry', 'geography'],
112
+ 'exactlyEquals',
113
+ 'Coordinates and coordinate order are the same as specified geometry.'
114
+ ],
115
+ [
116
+ '&&',
117
+ ['geometry', 'geography'],
118
+ 'bboxIntersects2D',
119
+ "2D bounding box intersects the specified geometry's 2D bounding box."
120
+ ],
121
+ [
122
+ '&&&',
123
+ ['geometry'],
124
+ 'bboxIntersectsND',
125
+ "n-D bounding box intersects the specified geometry's n-D bounding box."
126
+ ],
127
+ [
128
+ '&<',
129
+ ['geometry'],
130
+ 'bboxOverlapsOrLeftOf',
131
+ "Bounding box overlaps or is to the left of the specified geometry's bounding box."
132
+ ],
133
+ [
134
+ '&<|',
135
+ ['geometry'],
136
+ 'bboxOverlapsOrBelow',
137
+ "Bounding box overlaps or is below the specified geometry's bounding box."
138
+ ],
139
+ [
140
+ '&>',
141
+ ['geometry'],
142
+ 'bboxOverlapsOrRightOf',
143
+ "Bounding box overlaps or is to the right of the specified geometry's bounding box."
144
+ ],
145
+ [
146
+ '|&>',
147
+ ['geometry'],
148
+ 'bboxOverlapsOrAbove',
149
+ "Bounding box overlaps or is above the specified geometry's bounding box."
150
+ ],
151
+ [
152
+ '<<',
153
+ ['geometry'],
154
+ 'bboxLeftOf',
155
+ "Bounding box is strictly to the left of the specified geometry's bounding box."
156
+ ],
157
+ [
158
+ '<<|',
159
+ ['geometry'],
160
+ 'bboxBelow',
161
+ "Bounding box is strictly below the specified geometry's bounding box."
162
+ ],
163
+ [
164
+ '>>',
165
+ ['geometry'],
166
+ 'bboxRightOf',
167
+ "Bounding box is strictly to the right of the specified geometry's bounding box."
168
+ ],
169
+ [
170
+ '|>>',
171
+ ['geometry'],
172
+ 'bboxAbove',
173
+ "Bounding box is strictly above the specified geometry's bounding box."
174
+ ],
175
+ [
176
+ '~',
177
+ ['geometry'],
178
+ 'bboxContains',
179
+ "Bounding box contains the specified geometry's bounding box."
180
+ ],
181
+ [
182
+ '~=',
183
+ ['geometry'],
184
+ 'bboxEquals',
185
+ "Bounding box is the same as the specified geometry's bounding box."
186
+ ]
187
+ ];
188
+ /**
189
+ * Creates the PostGIS spatial filter operator factory.
190
+ *
191
+ * This factory dynamically generates operator registrations based on the
192
+ * PostGIS extension info discovered during the build phase. It discovers
193
+ * all geometry/geography GQL type names and creates ST_ function-based
194
+ * and SQL operator-based filter operators for each.
195
+ *
196
+ * Registered via the declarative `connectionFilterOperatorFactories` API
197
+ * in the GraphilePostgisPreset.
198
+ */
199
+ function createPostgisOperatorFactory() {
200
+ return (build) => {
201
+ const postgisInfo = build.pgGISExtensionInfo;
202
+ if (!postgisInfo) {
203
+ return [];
204
+ }
205
+ const { inflection } = build;
206
+ const { schemaName, geometryCodec, geographyCodec } = postgisInfo;
207
+ // Collect all GQL type names for geometry and geography
208
+ const gqlTypeNamesByBase = {
209
+ geometry: [],
210
+ geography: []
211
+ };
212
+ const codecPairs = [['geometry', geometryCodec]];
213
+ if (geographyCodec) {
214
+ codecPairs.push(['geography', geographyCodec]);
215
+ }
216
+ for (const [baseKey, codec] of codecPairs) {
217
+ const typeName = codec.name;
218
+ gqlTypeNamesByBase[baseKey].push(inflection.gisInterfaceName(typeName));
219
+ for (const subtype of constants_1.CONCRETE_SUBTYPES) {
220
+ for (const hasZ of [false, true]) {
221
+ for (const hasM of [false, true]) {
222
+ gqlTypeNamesByBase[baseKey].push(inflection.gisType(typeName, subtype, hasZ, hasM, 0));
223
+ }
224
+ }
225
+ }
226
+ }
227
+ const allSpecs = [];
228
+ // Process function-based operators
229
+ for (const [fn, baseTypes, operatorName, description] of FUNCTION_SPECS) {
230
+ for (const baseType of baseTypes) {
231
+ const sqlGisFunction = pg_sql2_1.default.identifier(schemaName, fn.toLowerCase());
232
+ allSpecs.push({
233
+ typeNames: gqlTypeNamesByBase[baseType],
234
+ operatorName,
235
+ description,
236
+ resolve: (i, v) => pg_sql2_1.default.fragment `${sqlGisFunction}(${i}, ${v})`
237
+ });
238
+ }
239
+ }
240
+ // Process SQL operator-based operators
241
+ for (const [op, baseTypes, operatorName, description] of OPERATOR_SPECS) {
242
+ if (!ALLOWED_SQL_OPERATORS.has(op)) {
243
+ throw new Error(`Unexpected SQL operator: ${op}`);
244
+ }
245
+ for (const baseType of baseTypes) {
246
+ allSpecs.push({
247
+ typeNames: gqlTypeNamesByBase[baseType],
248
+ operatorName,
249
+ description,
250
+ resolve: (i, v) => pg_sql2_1.default.fragment `${i} ${pg_sql2_1.default.raw(op)} ${v}`
251
+ });
252
+ }
253
+ }
254
+ // Sort by operator name for deterministic schema output
255
+ allSpecs.sort((a, b) => a.operatorName.localeCompare(b.operatorName));
256
+ // Convert to ConnectionFilterOperatorRegistration format.
257
+ // Each InternalSpec may target multiple type names; we expand each
258
+ // into individual registrations keyed by typeName.
259
+ const registrations = [];
260
+ for (const spec of allSpecs) {
261
+ for (const typeName of spec.typeNames) {
262
+ registrations.push({
263
+ typeNames: typeName,
264
+ operatorName: spec.operatorName,
265
+ spec: {
266
+ description: spec.description,
267
+ resolveType: (fieldType) => fieldType,
268
+ resolve(sqlIdentifier, sqlValue, _input, _$where, _details) {
269
+ return spec.resolve(sqlIdentifier, sqlValue);
270
+ }
271
+ },
272
+ });
273
+ }
274
+ }
275
+ return registrations;
276
+ };
277
+ }
package/preset.d.ts CHANGED
@@ -5,6 +5,13 @@ import type { GraphileConfig } from 'graphile-config';
5
5
  * A preset that includes all PostGIS plugins for PostGraphile v5.
6
6
  * Use this as the recommended way to add PostGIS support.
7
7
  *
8
+ * Includes:
9
+ * - Geometry/geography type codecs and GeoJSON scalar
10
+ * - PostGIS extension auto-detection
11
+ * - PostGIS inflection (type names for subtypes, Z/M variants)
12
+ * - Geometry field plugins (coordinates, GeoJSON output)
13
+ * - Connection filter operators (26 spatial operators via declarative factory API)
14
+ *
8
15
  * @example
9
16
  * ```typescript
10
17
  * import { GraphilePostgisPreset } from 'graphile-postgis';
package/preset.js CHANGED
@@ -6,12 +6,20 @@ const inflection_1 = require("./plugins/inflection");
6
6
  const detect_extension_1 = require("./plugins/detect-extension");
7
7
  const register_types_1 = require("./plugins/register-types");
8
8
  const geometry_fields_1 = require("./plugins/geometry-fields");
9
+ const connection_filter_operators_1 = require("./plugins/connection-filter-operators");
9
10
  /**
10
11
  * GraphilePostgisPreset
11
12
  *
12
13
  * A preset that includes all PostGIS plugins for PostGraphile v5.
13
14
  * Use this as the recommended way to add PostGIS support.
14
15
  *
16
+ * Includes:
17
+ * - Geometry/geography type codecs and GeoJSON scalar
18
+ * - PostGIS extension auto-detection
19
+ * - PostGIS inflection (type names for subtypes, Z/M variants)
20
+ * - Geometry field plugins (coordinates, GeoJSON output)
21
+ * - Connection filter operators (26 spatial operators via declarative factory API)
22
+ *
15
23
  * @example
16
24
  * ```typescript
17
25
  * import { GraphilePostgisPreset } from 'graphile-postgis';
@@ -28,6 +36,11 @@ exports.GraphilePostgisPreset = {
28
36
  detect_extension_1.PostgisExtensionDetectionPlugin,
29
37
  register_types_1.PostgisRegisterTypesPlugin,
30
38
  geometry_fields_1.PostgisGeometryFieldsPlugin
31
- ]
39
+ ],
40
+ schema: {
41
+ connectionFilterOperatorFactories: [
42
+ (0, connection_filter_operators_1.createPostgisOperatorFactory)(),
43
+ ],
44
+ },
32
45
  };
33
46
  exports.default = exports.GraphilePostgisPreset;