graphile-postgis 1.1.1 → 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,325 @@
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.PostgisRegisterTypesPlugin = void 0;
7
+ require("graphile-build");
8
+ require("graphile-build-pg");
9
+ const pg_sql2_1 = __importDefault(require("pg-sql2"));
10
+ const constants_1 = require("../constants");
11
+ const utils_1 = require("../utils");
12
+ // Import types.ts for the Build/Inflection/Scope augmentation side effects
13
+ require("../types");
14
+ /**
15
+ * PostgisRegisterTypesPlugin
16
+ *
17
+ * The core plugin that:
18
+ * 1. Registers a GeoJSON scalar type
19
+ * 2. Creates GraphQL interfaces for geometry/geography base types
20
+ * 3. Creates GraphQL interfaces for each dimension combination (XY, XYZ, XYM, XYZM)
21
+ * 4. Creates concrete GraphQL object types for each subtype/dimension combo
22
+ * 5. Registers codec-to-type mappings so PostGraphile knows how to handle
23
+ * geometry/geography columns
24
+ *
25
+ * In v5, type registration is done during the init hook using
26
+ * build.registerObjectType / build.registerInterfaceType / build.registerScalarType.
27
+ *
28
+ * The SQL tweak wraps geometry/geography values in json_build_object() containing
29
+ * __gisType, __srid, and __geojson fields, which downstream resolvers use.
30
+ */
31
+ exports.PostgisRegisterTypesPlugin = {
32
+ name: 'PostgisRegisterTypesPlugin',
33
+ version: '2.0.0',
34
+ description: 'Registers PostGIS GeoJSON scalar and geometry/geography types',
35
+ after: ['PostgisExtensionDetectionPlugin', 'PostgisInflectionPlugin'],
36
+ schema: {
37
+ hooks: {
38
+ init(_, build) {
39
+ const postgisInfo = build.pgGISExtensionInfo;
40
+ if (!postgisInfo) {
41
+ return _;
42
+ }
43
+ const { inflection, graphql: { GraphQLInt, GraphQLNonNull, Kind } } = build;
44
+ const constructedTypes = build.pgGISGraphQLTypesByCodecAndSubtype;
45
+ if (!constructedTypes) {
46
+ return _;
47
+ }
48
+ const { geometryCodec, geographyCodec } = postgisInfo;
49
+ // Register the GeoJSON scalar type
50
+ build.registerScalarType('GeoJSON', {}, () => ({
51
+ description: 'The `GeoJSON` scalar type represents GeoJSON values as specified by ' +
52
+ '[RFC 7946](https://tools.ietf.org/html/rfc7946).',
53
+ serialize: (value) => value,
54
+ parseValue: (value) => {
55
+ if (value === null || value === undefined)
56
+ return value;
57
+ if (typeof value !== 'object' || Array.isArray(value)) {
58
+ throw new TypeError('GeoJSON must be an object');
59
+ }
60
+ const obj = value;
61
+ if (typeof obj.type !== 'string') {
62
+ throw new TypeError('GeoJSON must have a "type" string property');
63
+ }
64
+ if (obj.type === 'Feature' || obj.type === 'FeatureCollection') {
65
+ throw new TypeError(`GeoJSON type "${obj.type}" is not supported for PostGIS geometry input. Extract the geometry from your Feature first.`);
66
+ }
67
+ const validTypes = ['Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'];
68
+ if (!validTypes.includes(obj.type)) {
69
+ throw new TypeError(`GeoJSON type "${obj.type}" is not a recognized GeoJSON type`);
70
+ }
71
+ return value;
72
+ },
73
+ parseLiteral(ast, variables) {
74
+ return parseLiteralGeoJSON(ast, variables, Kind);
75
+ }
76
+ }), 'PostgisRegisterTypesPlugin registering GeoJSON scalar');
77
+ // Process geometry and (optionally) geography types
78
+ const gisCodecs = [geometryCodec, geographyCodec].filter((c) => c !== null);
79
+ for (const gisCodec of gisCodecs) {
80
+ const key = gisCodec.name;
81
+ const typeName = gisCodec.name;
82
+ if (!constructedTypes[key]) {
83
+ constructedTypes[key] = {};
84
+ }
85
+ // Register the main interface (no dimensional constraint)
86
+ const mainInterfaceName = inflection.gisInterfaceName(typeName);
87
+ build.registerInterfaceType(mainInterfaceName, {
88
+ isPgGISInterface: true,
89
+ pgGISCodecName: typeName,
90
+ pgGISZMFlag: -1
91
+ }, () => ({
92
+ description: `All ${typeName} types implement this interface`,
93
+ fields: () => {
94
+ const geoJsonType = build.getTypeByName('GeoJSON');
95
+ if (!geoJsonType) {
96
+ throw new Error('PostGIS: GeoJSON scalar type not found.');
97
+ }
98
+ return {
99
+ [inflection.geojsonFieldName()]: {
100
+ type: geoJsonType,
101
+ description: 'Converts the object to GeoJSON'
102
+ },
103
+ srid: {
104
+ type: new GraphQLNonNull(GraphQLInt),
105
+ description: 'Spatial reference identifier (SRID)'
106
+ }
107
+ };
108
+ },
109
+ resolveType(value) {
110
+ const gisTypeKey = value.__gisType;
111
+ const resolvedTypeName = constructedTypes[key]?.[gisTypeKey];
112
+ if (typeof resolvedTypeName === 'string') {
113
+ return resolvedTypeName;
114
+ }
115
+ throw new Error(`PostGIS: Could not resolve type for __gisType="${gisTypeKey}" on codec="${key}". Known types: ${Object.keys(constructedTypes[key] ?? {}).join(', ')}`);
116
+ }
117
+ }), `PostgisRegisterTypesPlugin registering ${mainInterfaceName} interface`);
118
+ // Register dimension interfaces (XY, XYZ, XYM, XYZM)
119
+ for (const hasZ of [false, true]) {
120
+ for (const hasM of [false, true]) {
121
+ const zmflag = (hasZ ? 2 : 0) + (hasM ? 1 : 0);
122
+ const coords = { 0: 'XY', 1: 'XYM', 2: 'XYZ', 3: 'XYZM' };
123
+ const dimInterfaceName = inflection.gisDimensionInterfaceName(typeName, hasZ, hasM);
124
+ build.registerInterfaceType(dimInterfaceName, {
125
+ isPgGISDimensionInterface: true,
126
+ pgGISCodecName: typeName,
127
+ pgGISZMFlag: zmflag
128
+ }, () => ({
129
+ description: `All ${typeName} ${coords[zmflag]} types implement this interface`,
130
+ fields: () => {
131
+ const geoJsonType = build.getTypeByName('GeoJSON');
132
+ if (!geoJsonType) {
133
+ throw new Error('PostGIS: GeoJSON scalar type not found.');
134
+ }
135
+ return {
136
+ [inflection.geojsonFieldName()]: {
137
+ type: geoJsonType,
138
+ description: 'Converts the object to GeoJSON'
139
+ },
140
+ srid: {
141
+ type: new GraphQLNonNull(GraphQLInt),
142
+ description: 'Spatial reference identifier (SRID)'
143
+ }
144
+ };
145
+ },
146
+ resolveType(value) {
147
+ const gisTypeKey = value.__gisType;
148
+ const concreteTypeName = constructedTypes[key]?.[gisTypeKey];
149
+ if (typeof concreteTypeName === 'string') {
150
+ return concreteTypeName;
151
+ }
152
+ throw new Error(`PostGIS: Could not resolve type for __gisType="${gisTypeKey}" on codec="${key}". Known types: ${Object.keys(constructedTypes[key] ?? {}).join(', ')}`);
153
+ }
154
+ }), `PostgisRegisterTypesPlugin registering ${dimInterfaceName} interface`);
155
+ // Register concrete object types for each subtype + this dimension
156
+ for (const subtype of constants_1.CONCRETE_SUBTYPES) {
157
+ const concreteTypeName = inflection.gisType(typeName, subtype, hasZ, hasM, 0);
158
+ const typeModifier = (0, utils_1.getGISTypeModifier)(subtype, hasZ, hasM, 0);
159
+ const typeDetails = (0, utils_1.getGISTypeDetails)(typeModifier);
160
+ build.registerObjectType(concreteTypeName, {
161
+ isPgGISType: true,
162
+ pgGISCodecName: typeName,
163
+ pgGISTypeDetails: typeDetails
164
+ }, () => ({
165
+ description: `A PostGIS ${typeName} ${(0, utils_1.getGISTypeName)(subtype, hasZ, hasM)} type`,
166
+ interfaces: () => [
167
+ build.getTypeByName(mainInterfaceName),
168
+ build.getTypeByName(dimInterfaceName)
169
+ ].filter(Boolean),
170
+ fields: () => {
171
+ const geoJsonType = build.getTypeByName('GeoJSON');
172
+ if (!geoJsonType) {
173
+ throw new Error('PostGIS: GeoJSON scalar type not found.');
174
+ }
175
+ return {
176
+ [inflection.geojsonFieldName()]: {
177
+ type: geoJsonType,
178
+ description: 'Converts the object to GeoJSON',
179
+ resolve(data) {
180
+ return data.__geojson;
181
+ }
182
+ },
183
+ srid: {
184
+ type: new GraphQLNonNull(GraphQLInt),
185
+ description: 'Spatial reference identifier (SRID)',
186
+ resolve(data) {
187
+ return data.__srid;
188
+ }
189
+ }
190
+ };
191
+ }
192
+ }), `PostgisRegisterTypesPlugin registering ${concreteTypeName} type`);
193
+ // Track the type by its gisTypeKey for resolveType lookups
194
+ const gisTypeKey = (0, utils_1.getGISTypeName)(subtype, hasZ, hasM);
195
+ constructedTypes[key][gisTypeKey] = concreteTypeName;
196
+ }
197
+ // Map the Geometry subtype (0) for this dimension to the dimension interface
198
+ const geomDimKey = (0, utils_1.getGISTypeName)(constants_1.GisSubtype.Geometry, hasZ, hasM);
199
+ constructedTypes[key][geomDimKey] = dimInterfaceName;
200
+ }
201
+ }
202
+ // Map null/unspecified modifier to the main interface
203
+ constructedTypes[key][-1] = mainInterfaceName;
204
+ }
205
+ // Register type mappings so PostGraphile knows what GraphQL types to
206
+ // use for geometry/geography columns.
207
+ //
208
+ // Without BOTH input AND output mappings, PgAttributesPlugin silently
209
+ // omits geometry columns from the schema:
210
+ // - Input: GeoJSON scalar (accepts GeoJSON objects)
211
+ // - Output: The main interface type (e.g. GeometryInterface), which uses
212
+ // resolveType to dispatch to concrete types (GeometryPoint, etc.)
213
+ const { setGraphQLTypeForPgCodec } = build;
214
+ if (typeof setGraphQLTypeForPgCodec === 'function') {
215
+ for (const gisCodec of gisCodecs) {
216
+ const mainInterfaceName = inflection.gisInterfaceName(gisCodec.name);
217
+ setGraphQLTypeForPgCodec(gisCodec, 'input', 'GeoJSON');
218
+ setGraphQLTypeForPgCodec(gisCodec, 'output', mainInterfaceName);
219
+ }
220
+ }
221
+ return _;
222
+ },
223
+ build(build) {
224
+ const postgisInfo = build.pgGISExtensionInfo;
225
+ if (!postgisInfo) {
226
+ return build;
227
+ }
228
+ const { schemaName } = postgisInfo;
229
+ const constructedTypes = build.pgGISGraphQLTypesByCodecAndSubtype;
230
+ if (!constructedTypes) {
231
+ return build;
232
+ }
233
+ return build.extend(build, {
234
+ getPostgisTypeByGeometryType(gisCodecName, subtype, hasZ = false, hasM = false, _srid = 0) {
235
+ const gisTypeKey = (0, utils_1.getGISTypeName)(subtype, hasZ, hasM);
236
+ const resolvedTypeName = constructedTypes[gisCodecName]?.[gisTypeKey];
237
+ if (typeof resolvedTypeName === 'string') {
238
+ return build.getTypeByName(resolvedTypeName);
239
+ }
240
+ return undefined;
241
+ },
242
+ pgGISWrapExpression(fragment) {
243
+ // PostGIS function names MUST be lowercase for PostgreSQL identifier matching
244
+ const params = [
245
+ pg_sql2_1.default.literal('__gisType'),
246
+ pg_sql2_1.default.fragment `${pg_sql2_1.default.identifier(schemaName, 'geometrytype')}(${fragment})`,
247
+ pg_sql2_1.default.literal('__srid'),
248
+ pg_sql2_1.default.fragment `${pg_sql2_1.default.identifier(schemaName, 'st_srid')}(${fragment})`,
249
+ pg_sql2_1.default.literal('__geojson'),
250
+ pg_sql2_1.default.fragment `${pg_sql2_1.default.identifier(schemaName, 'st_asgeojson')}(${fragment})::JSON`
251
+ ];
252
+ return pg_sql2_1.default.fragment `(case when ${fragment} is null then null else json_build_object(
253
+ ${pg_sql2_1.default.join(params, ', ')}
254
+ ) end)`;
255
+ },
256
+ pgGISFromGeoJSON(value, codecName) {
257
+ const jsonStr = pg_sql2_1.default.value(JSON.stringify(value));
258
+ if (codecName === 'geography') {
259
+ return pg_sql2_1.default.fragment `${pg_sql2_1.default.identifier(schemaName, 'st_geomfromgeojson')}(${jsonStr}::text)::${pg_sql2_1.default.identifier(schemaName, 'geography')}`;
260
+ }
261
+ return pg_sql2_1.default.fragment `${pg_sql2_1.default.identifier(schemaName, 'st_geomfromgeojson')}(${jsonStr}::text)`;
262
+ }
263
+ }, 'PostgisRegisterTypesPlugin adding PostGIS helpers to build');
264
+ },
265
+ // Add all registered PostGIS concrete types to the schema so they are
266
+ // discoverable for resolveType on the interfaces. Types registered via
267
+ // build.registerObjectType() during init are lazy — they are only
268
+ // materialized when getTypeByName() is called.
269
+ //
270
+ // We MUST use GraphQLSchema_types (not GraphQLSchema) because the
271
+ // GraphQLSchema hook's types property gets overwritten by the
272
+ // GraphQLSchema_types hook that runs immediately after it.
273
+ GraphQLSchema_types(types, build) {
274
+ const constructedTypes = build.pgGISGraphQLTypesByCodecAndSubtype;
275
+ if (!constructedTypes) {
276
+ return types;
277
+ }
278
+ for (const codecTypes of Object.values(constructedTypes)) {
279
+ for (const typeName of Object.values(codecTypes)) {
280
+ if (typeof typeName === 'string') {
281
+ const graphqlType = build.getTypeByName(typeName);
282
+ if (graphqlType && !types.includes(graphqlType)) {
283
+ types.push(graphqlType);
284
+ }
285
+ }
286
+ }
287
+ }
288
+ return types;
289
+ }
290
+ }
291
+ }
292
+ };
293
+ /**
294
+ * Recursively parses a GeoJSON literal from a GraphQL AST.
295
+ */
296
+ function parseLiteralGeoJSON(ast, variables, Kind, depth = 0) {
297
+ if (depth > 32) {
298
+ throw new Error('GeoJSON input exceeds maximum nesting depth');
299
+ }
300
+ switch (ast.kind) {
301
+ case Kind.STRING:
302
+ case Kind.BOOLEAN:
303
+ return ast.value;
304
+ case Kind.INT:
305
+ case Kind.FLOAT:
306
+ return parseFloat(ast.value);
307
+ case Kind.OBJECT: {
308
+ const value = Object.create(null);
309
+ ast.fields.forEach((field) => {
310
+ value[field.name.value] = parseLiteralGeoJSON(field.value, variables, Kind, depth + 1);
311
+ });
312
+ return value;
313
+ }
314
+ case Kind.LIST:
315
+ return ast.values.map((n) => parseLiteralGeoJSON(n, variables, Kind, depth + 1));
316
+ case Kind.NULL:
317
+ return null;
318
+ case Kind.VARIABLE: {
319
+ const variableName = ast.name.value;
320
+ return variables ? variables[variableName] : undefined;
321
+ }
322
+ default:
323
+ return undefined;
324
+ }
325
+ }
package/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;
package/preset.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GraphilePostgisPreset = void 0;
4
+ const codec_1 = require("./plugins/codec");
5
+ const inflection_1 = require("./plugins/inflection");
6
+ const detect_extension_1 = require("./plugins/detect-extension");
7
+ const register_types_1 = require("./plugins/register-types");
8
+ const geometry_fields_1 = require("./plugins/geometry-fields");
9
+ /**
10
+ * GraphilePostgisPreset
11
+ *
12
+ * A preset that includes all PostGIS plugins for PostGraphile v5.
13
+ * Use this as the recommended way to add PostGIS support.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { GraphilePostgisPreset } from 'graphile-postgis';
18
+ *
19
+ * const preset = {
20
+ * extends: [GraphilePostgisPreset]
21
+ * };
22
+ * ```
23
+ */
24
+ exports.GraphilePostgisPreset = {
25
+ plugins: [
26
+ codec_1.PostgisCodecPlugin,
27
+ inflection_1.PostgisInflectionPlugin,
28
+ detect_extension_1.PostgisExtensionDetectionPlugin,
29
+ register_types_1.PostgisRegisterTypesPlugin,
30
+ geometry_fields_1.PostgisGeometryFieldsPlugin
31
+ ]
32
+ };
33
+ exports.default = exports.GraphilePostgisPreset;
package/types.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import type { Build, Inflection } from 'graphile-build';
2
- import type { PgExtension, PgIntrospectionResultsByKind, PgType } from 'graphile-build-pg';
3
- import type { SQL } from 'graphile-build-pg/node8plus/QueryBuilder';
4
- import type { GraphQLInputType, GraphQLInterfaceType, GraphQLObjectType, GraphQLOutputType, GraphQLType } from 'graphql';
1
+ import type { PgCodec } from '@dataplan/pg';
5
2
  import type { Geometry } from 'geojson';
3
+ import type { GraphQLInterfaceType, GraphQLObjectType } from 'graphql';
4
+ import type { SQL } from 'pg-sql2';
6
5
  import type { GisSubtype } from './constants';
7
6
  export interface GisTypeDetails {
8
7
  subtype: GisSubtype;
@@ -10,50 +9,76 @@ export interface GisTypeDetails {
10
9
  hasM: boolean;
11
10
  srid: number;
12
11
  }
13
- export type GisGraphQLType = GraphQLInterfaceType | GraphQLObjectType;
14
12
  export interface GisFieldValue {
15
13
  __gisType: string;
16
14
  __srid: number;
17
15
  __geojson: Geometry;
18
16
  }
19
- export interface PostgisInflection extends Inflection {
20
- gisType(type: PgType, subtype: GisSubtype, hasZ: boolean, hasM: boolean, srid?: number): string;
21
- gisInterfaceName(type: PgType): string;
22
- gisDimensionInterfaceName(type: PgType, hasZ: boolean, hasM: boolean): string;
23
- geojsonFieldName(): string;
24
- gisXFieldName(type: PgType): string;
25
- gisYFieldName(type: PgType): string;
26
- gisZFieldName(type: PgType): string;
17
+ /**
18
+ * PostGIS extension detection result stored on the build object.
19
+ */
20
+ export interface PostgisExtensionInfo {
21
+ /** The schema name where PostGIS is installed (e.g. 'public') */
22
+ schemaName: string;
23
+ /** The geometry codec from the registry */
24
+ geometryCodec: PgCodec;
25
+ /** The geography codec from the registry (optional — not all databases use geography columns) */
26
+ geographyCodec: PgCodec | null;
27
27
  }
28
- export type PgTweaksByTypeIdAndModifier = Record<string | number, Record<string | number, (fragment: SQL, resolveData: unknown) => SQL>>;
29
- export interface PgMapper {
30
- map: (value: unknown) => unknown;
31
- unmap: (value: unknown) => SQL;
32
- }
33
- export interface PostgisBuild extends Build {
34
- extend<TBase extends object, TExtension extends object>(base: TBase, extension: TExtension): TBase & TExtension;
35
- newWithHooks: <TType extends GraphQLOutputType, TConfig extends object>(constructor: new (config: TConfig) => TType, spec: TConfig, scope?: Record<string, unknown>) => TType;
36
- getTypeByName: (name: string) => GraphQLType | undefined;
37
- pgIntrospectionResultsByKind: PgIntrospectionResultsByKind;
38
- pgRegisterGqlTypeByTypeId: (typeId: string | number, generator: (set: Record<string, unknown>, typeModifier: number | null) => GraphQLOutputType) => void;
39
- pgRegisterGqlInputTypeByTypeId: (typeId: string | number, generator: () => GraphQLInputType) => void;
40
- pgGetGqlTypeByTypeIdAndModifier: (typeId: string | number, typeModifier: number | null) => GraphQLOutputType | GraphQLInterfaceType | null | undefined;
41
- pgTweaksByTypeIdAndModifer: PgTweaksByTypeIdAndModifier;
42
- pgSql: typeof import('graphile-build-pg/node8plus/QueryBuilder').sql;
43
- pg2gql: (value: unknown, type: PgType) => unknown;
44
- pg2GqlMapper: Record<string | number, PgMapper>;
45
- pgGISGraphQLTypesByTypeAndSubtype: Record<string | number, Record<string | number, GisGraphQLType>>;
46
- pgGISGraphQLInterfaceTypesByType: Record<string | number, Record<number, GraphQLInterfaceType>>;
47
- pgGISGeometryType?: PgType;
48
- pgGISGeographyType?: PgType;
49
- pgGISExtension?: PgExtension;
50
- pgGISIncludedTypes: GisGraphQLType[];
51
- pgGISIncludeType: (type: GisGraphQLType) => void;
52
- getPostgisTypeByGeometryType: (pgGISType: PgType, subtype: GisSubtype, hasZ?: boolean, hasM?: boolean, srid?: number) => GraphQLOutputType | GraphQLInterfaceType | null | undefined;
53
- inflection: PostgisInflection;
54
- }
55
- export interface GisScope {
56
- isPgGISType?: boolean;
57
- pgGISType?: PgType;
58
- pgGISTypeDetails?: GisTypeDetails;
28
+ /**
29
+ * Module augmentations for PostGraphile v5 types.
30
+ *
31
+ * These declare the custom properties that our PostGIS plugins add to the
32
+ * build object, inflection, and scope interfaces. This allows downstream
33
+ * code (including our own plugins) to use these properties without `any` casts.
34
+ */
35
+ declare global {
36
+ namespace GraphileBuild {
37
+ interface Build {
38
+ /** PostGIS extension info (set by PostgisExtensionDetectionPlugin) */
39
+ pgGISExtensionInfo?: PostgisExtensionInfo;
40
+ /** Map of codec name -> gisTypeKey -> GraphQL type name (for resolveType) */
41
+ pgGISGraphQLTypesByCodecAndSubtype?: Record<string, Record<string | number, string>>;
42
+ /** Gets a registered PostGIS GraphQL type by geometry type, subtype, and dimension */
43
+ getPostgisTypeByGeometryType?(gisCodecName: string, subtype: GisSubtype, hasZ?: boolean, hasM?: boolean, srid?: number): GraphQLObjectType | GraphQLInterfaceType | undefined;
44
+ /** Wraps a geometry/geography SQL expression in json_build_object() with metadata */
45
+ pgGISWrapExpression?(fragment: SQL): SQL;
46
+ /** Creates an SQL fragment to convert GeoJSON input to a geometry value */
47
+ pgGISFromGeoJSON?(value: Record<string, unknown>, codecName: string): SQL;
48
+ }
49
+ interface Inflection {
50
+ /** Generate GraphQL type name for a PostGIS concrete type */
51
+ gisType(typeName: string, subtype: GisSubtype, hasZ: boolean, hasM: boolean, srid?: number): string;
52
+ /** Generate interface name for a PostGIS base type (e.g. GeometryInterface) */
53
+ gisInterfaceName(typeName: string): string;
54
+ /** Generate dimension interface name (e.g. GeometryGeometryZ) */
55
+ gisDimensionInterfaceName(typeName: string, hasZ: boolean, hasM: boolean): string;
56
+ /** Generate GeoJSON field name */
57
+ geojsonFieldName(): string;
58
+ /** Generate X coordinate field name */
59
+ gisXFieldName(typeName: string): string;
60
+ /** Generate Y coordinate field name */
61
+ gisYFieldName(typeName: string): string;
62
+ /** Generate Z coordinate field name */
63
+ gisZFieldName(typeName: string): string;
64
+ }
65
+ interface ScopeObject {
66
+ /** Whether this is a PostGIS concrete type */
67
+ isPgGISType?: boolean;
68
+ /** The codec name (geometry/geography) this type belongs to */
69
+ pgGISCodecName?: string;
70
+ /** The type details (subtype, hasZ, hasM, srid) */
71
+ pgGISTypeDetails?: GisTypeDetails;
72
+ }
73
+ interface ScopeInterface {
74
+ /** Whether this is a PostGIS base interface */
75
+ isPgGISInterface?: boolean;
76
+ /** Whether this is a PostGIS dimension interface */
77
+ isPgGISDimensionInterface?: boolean;
78
+ /** The codec name (geometry/geography) this interface belongs to */
79
+ pgGISCodecName?: string;
80
+ /** The ZM flag (-1 for base, 0-3 for dimension) */
81
+ pgGISZMFlag?: number;
82
+ }
83
+ }
59
84
  }
package/utils.d.ts CHANGED
@@ -1,5 +1,21 @@
1
1
  import { GisSubtype } from './constants';
2
2
  import type { GisTypeDetails } from './types';
3
+ /**
4
+ * Decodes a PostGIS type modifier into its component parts.
5
+ *
6
+ * Implements the C macro bit layout from liblwgeom.h:
7
+ * - SRID: bits 8-28
8
+ * - Subtype: bits 2-7
9
+ * - hasZ: bit 1
10
+ * - hasM: bit 0
11
+ */
3
12
  export declare const getGISTypeDetails: (modifier: number) => GisTypeDetails;
13
+ /**
14
+ * Encodes PostGIS type details into a type modifier integer.
15
+ */
4
16
  export declare const getGISTypeModifier: (subtype: GisSubtype, hasZ: boolean, hasM: boolean, srid: number) => number;
17
+ /**
18
+ * Returns the GIS type name string for a given subtype and Z/M flags.
19
+ * E.g. "PointZ", "MultiPolygonZM", "LineString"
20
+ */
5
21
  export declare const getGISTypeName: (subtype: GisSubtype, hasZ: boolean, hasM: boolean) => string;
package/utils.js CHANGED
@@ -2,6 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getGISTypeName = exports.getGISTypeModifier = exports.getGISTypeDetails = void 0;
4
4
  const constants_1 = require("./constants");
5
+ /**
6
+ * Decodes a PostGIS type modifier into its component parts.
7
+ *
8
+ * Implements the C macro bit layout from liblwgeom.h:
9
+ * - SRID: bits 8-28
10
+ * - Subtype: bits 2-7
11
+ * - hasZ: bit 1
12
+ * - hasM: bit 0
13
+ */
5
14
  const getGISTypeDetails = (modifier) => {
6
15
  const allZeroesHopefully = modifier >> 24;
7
16
  if (allZeroesHopefully !== 0) {
@@ -27,14 +36,12 @@ const getGISTypeDetails = (modifier) => {
27
36
  throw new Error(`Unsupported PostGIS modifier, expected 0-7, received ${subtypeNumeric} (${modifier})`);
28
37
  }
29
38
  const subtype = subtypeNumeric;
30
- return {
31
- subtype,
32
- hasZ,
33
- hasM,
34
- srid
35
- };
39
+ return { subtype, hasZ, hasM, srid };
36
40
  };
37
41
  exports.getGISTypeDetails = getGISTypeDetails;
42
+ /**
43
+ * Encodes PostGIS type details into a type modifier integer.
44
+ */
38
45
  const getGISTypeModifier = (subtype, hasZ, hasM, srid) => {
39
46
  // Ref: https://github.com/postgis/postgis/blob/2.5.2/liblwgeom/liblwgeom.h.in#L156-L173
40
47
  // #define TYPMOD_SET_SRID(typmod, srid) ((typmod) = (((typmod) & 0xE00000FF) | ((srid & 0x001FFFFF)<<8)))
@@ -47,6 +54,10 @@ const getGISTypeModifier = (subtype, hasZ, hasM, srid) => {
47
54
  (hasM ? 0x00000001 : 0));
48
55
  };
49
56
  exports.getGISTypeModifier = getGISTypeModifier;
57
+ /**
58
+ * Returns the GIS type name string for a given subtype and Z/M flags.
59
+ * E.g. "PointZ", "MultiPolygonZM", "LineString"
60
+ */
50
61
  const getGISTypeName = (subtype, hasZ, hasM) => {
51
62
  return `${constants_1.GIS_SUBTYPE_NAME[subtype]}${hasZ ? 'Z' : ''}${hasM ? 'M' : ''}`;
52
63
  };
@@ -1,3 +0,0 @@
1
- import type { Plugin } from 'graphile-build';
2
- declare const PostgisExtensionDetectionPlugin: Plugin;
3
- export default PostgisExtensionDetectionPlugin;
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const PostgisExtensionDetectionPlugin = (builder) => {
4
- builder.hook('build', (build) => {
5
- const postgisBuild = build;
6
- const { pgIntrospectionResultsByKind: introspectionResultsByKind } = postgisBuild;
7
- const pgGISExtension = introspectionResultsByKind.extension.find((extension) => extension.name === 'postgis');
8
- // Check we have the postgis extension
9
- if (!pgGISExtension) {
10
- console.warn('PostGIS extension not found in database; skipping');
11
- return postgisBuild;
12
- }
13
- // Extract the geography and geometry types
14
- const pgGISGeometryType = introspectionResultsByKind.type.find((type) => type.name === 'geometry' && type.namespaceId === pgGISExtension.namespaceId);
15
- const pgGISGeographyType = introspectionResultsByKind.type.find((type) => type.name === 'geography' && type.namespaceId === pgGISExtension.namespaceId);
16
- if (!pgGISGeographyType || !pgGISGeometryType) {
17
- throw new Error("PostGIS is installed, but we couldn't find the geometry/geography types!");
18
- }
19
- return postgisBuild.extend(postgisBuild, {
20
- pgGISGraphQLTypesByTypeAndSubtype: {},
21
- pgGISGraphQLInterfaceTypesByType: {},
22
- pgGISGeometryType,
23
- pgGISGeographyType,
24
- pgGISExtension
25
- });
26
- });
27
- };
28
- exports.default = PostgisExtensionDetectionPlugin;
@@ -1,3 +0,0 @@
1
- import type { Plugin } from 'graphile-build';
2
- declare const PostgisInflectionPlugin: Plugin;
3
- export default PostgisInflectionPlugin;
@@ -1,36 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const constants_1 = require("./constants");
4
- const PostgisInflectionPlugin = (builder) => {
5
- builder.hook('inflection', (inflection) => {
6
- return {
7
- ...inflection,
8
- gisType(type, subtype, hasZ, hasM, _srid) {
9
- return this.upperCamelCase([type.name, constants_1.SUBTYPE_STRING_BY_SUBTYPE[subtype], hasZ ? 'z' : null, hasM ? 'm' : null]
10
- .filter(Boolean)
11
- .join('-'));
12
- },
13
- gisInterfaceName(type) {
14
- return this.upperCamelCase(`${type.name}-interface`);
15
- },
16
- gisDimensionInterfaceName(type, hasZ, hasM) {
17
- return this.upperCamelCase([type.name, constants_1.SUBTYPE_STRING_BY_SUBTYPE[constants_1.GisSubtype.Geometry], hasZ ? 'z' : null, hasM ? 'm' : null]
18
- .filter(Boolean)
19
- .join('-'));
20
- },
21
- geojsonFieldName() {
22
- return 'geojson';
23
- },
24
- gisXFieldName(_type) {
25
- return _type.name === 'geography' ? 'longitude' : 'x';
26
- },
27
- gisYFieldName(_type) {
28
- return _type.name === 'geography' ? 'latitude' : 'y';
29
- },
30
- gisZFieldName(_type) {
31
- return _type.name === 'geography' ? 'height' : 'z';
32
- }
33
- };
34
- });
35
- };
36
- exports.default = PostgisInflectionPlugin;