Skip to content

Commit 4975a23

Browse files
authored
Merge pull request #1006 from constructive-io/fix/spatial-relation-casing
fix(graphile-postgis): camelCase @spatialRelation field + PascalCase filter type
2 parents 01775e1 + 78610ae commit 4975a23

3 files changed

Lines changed: 52 additions & 3 deletions

File tree

graphile/graphile-connection-filter/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@
4949
* ```
5050
*/
5151

52+
// Load the global type augmentations (inflection methods, build/scope
53+
// properties) so that downstream satellite plugins which `import
54+
// 'graphile-connection-filter'` pick up the `filterType`/`filterManyType`/
55+
// etc. type extensions without having to reach into the package's internal
56+
// file layout.
57+
import './augmentations';
58+
5259
export { ConnectionFilterPreset } from './preset';
5360

5461
// Re-export all plugins for granular use

graphile/graphile-postgis/__tests__/spatial-relations.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,16 @@ function buildMockRegistry(
6767
}
6868

6969
function makeBuild(registry: any): any {
70+
// Minimal inflection double — only `camelCase` is consulted by
71+
// `collectSpatialRelations` (to normalize the parametric arg name).
72+
const inflection = {
73+
camelCase(str: string): string {
74+
return str.replace(/[-_](.)/g, (_, c: string) => c.toUpperCase());
75+
},
76+
};
7077
return {
7178
input: { pgRegistry: registry },
79+
inflection,
7280
};
7381
}
7482

@@ -289,6 +297,27 @@ describe('collectSpatialRelations', () => {
289297
expect(rel.paramFieldName).toBe('distance');
290298
});
291299

300+
it('camelCases snake_case parametric arg names', () => {
301+
// The @spatialRelation tag grammar accepts any [A-Za-z_][A-Za-z0-9_]*
302+
// identifier for the parametric arg; the GraphQL field we expose for
303+
// it must follow the same camelCase convention as every other field.
304+
const registry = buildMockRegistry({
305+
clinics: {
306+
pk: ['id'],
307+
attributes: {
308+
id: { base: 'int4' },
309+
location: {
310+
base: 'geometry',
311+
spatialRelation:
312+
'nearbyClinic clinics.location st_dwithin travel_distance',
313+
},
314+
},
315+
},
316+
});
317+
const [rel] = collectSpatialRelations(makeBuild(registry));
318+
expect(rel.paramFieldName).toBe('travelDistance');
319+
});
320+
292321
it('supports multiple tags on the same column (string[] form)', () => {
293322
const registry = buildMockRegistry({
294323
counties: {

graphile/graphile-postgis/src/plugins/spatial-relations.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,13 @@ export function collectSpatialRelations(build: any): SpatialRelationInfo[] {
322322
const pgRegistry = build.input?.pgRegistry;
323323
if (!pgRegistry) return [];
324324

325+
// Inflection is used to normalize user-supplied identifiers (the
326+
// parametric arg name, e.g. `travel_distance` → `travelDistance`) into the
327+
// GraphQL casing conventions. Fall back to identity if not available
328+
// (e.g. when invoked from unit tests with a stub build).
329+
const camelCase: (s: string) => string =
330+
build.inflection?.camelCase?.bind(build.inflection) ?? ((s: string) => s);
331+
325332
const relations: SpatialRelationInfo[] = [];
326333

327334
for (const resource of Object.values(pgRegistry.pgResources) as any[]) {
@@ -400,7 +407,7 @@ export function collectSpatialRelations(build: any): SpatialRelationInfo[] {
400407
targetResource: target.resource,
401408
targetAttributeName: target.attributeName,
402409
operator: OPERATOR_REGISTRY[parsed.operator],
403-
paramFieldName: parsed.paramName,
410+
paramFieldName: parsed.paramName ? camelCase(parsed.paramName) : null,
404411
isSelfRelation,
405412
ownerPkAttributes,
406413
targetPkAttributes,
@@ -431,7 +438,10 @@ export function collectSpatialRelations(build: any): SpatialRelationInfo[] {
431438
function spatialFilterTypeName(build: any, rel: SpatialRelationInfo): string {
432439
const { inflection } = build;
433440
const ownerTypeName = inflection.tableType(rel.ownerCodec);
434-
const rel0 = rel.relationName.charAt(0).toUpperCase() + rel.relationName.slice(1);
441+
// Normalize the user-supplied relation name (which may be snake_case,
442+
// kebab-case, or mixed) into PascalCase so the type name is consistent
443+
// with every other generated GraphQL type name.
444+
const rel0 = inflection.upperCamelCase(rel.relationName);
435445
return `${ownerTypeName}Spatial${rel0}Filter`;
436446
}
437447

@@ -638,7 +648,10 @@ export const PostgisSpatialRelationsPlugin: GraphileConfig.Plugin = {
638648
const FilterType = build.getTypeByName(filterTypeName);
639649
if (!FilterType) continue;
640650

641-
const fieldName = rel.relationName;
651+
// Normalize the user-supplied relation name (which may be
652+
// snake_case, kebab-case, or mixed) into camelCase so the GraphQL
653+
// field name matches the casing of every other generated field.
654+
const fieldName = inflection.camelCase(rel.relationName);
642655
// Avoid clobbering fields an upstream plugin may have registered
643656
// (e.g. an FK-derived relation with the same name).
644657
if (fields[fieldName]) {

0 commit comments

Comments
 (0)