Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions graphile/graphile-connection-filter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
* ```
*/

// Load the global type augmentations (inflection methods, build/scope
// properties) so that downstream satellite plugins which `import
// 'graphile-connection-filter'` pick up the `filterType`/`filterManyType`/
// etc. type extensions without having to reach into the package's internal
// file layout.
import './augmentations';

export { ConnectionFilterPreset } from './preset';

// Re-export all plugins for granular use
Expand Down
29 changes: 29 additions & 0 deletions graphile/graphile-postgis/__tests__/spatial-relations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,16 @@ function buildMockRegistry(
}

function makeBuild(registry: any): any {
// Minimal inflection double — only `camelCase` is consulted by
// `collectSpatialRelations` (to normalize the parametric arg name).
const inflection = {
camelCase(str: string): string {
return str.replace(/[-_](.)/g, (_, c: string) => c.toUpperCase());
},
};
return {
input: { pgRegistry: registry },
inflection,
};
}

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

it('camelCases snake_case parametric arg names', () => {
// The @spatialRelation tag grammar accepts any [A-Za-z_][A-Za-z0-9_]*
// identifier for the parametric arg; the GraphQL field we expose for
// it must follow the same camelCase convention as every other field.
const registry = buildMockRegistry({
clinics: {
pk: ['id'],
attributes: {
id: { base: 'int4' },
location: {
base: 'geometry',
spatialRelation:
'nearbyClinic clinics.location st_dwithin travel_distance',
},
},
},
});
const [rel] = collectSpatialRelations(makeBuild(registry));
expect(rel.paramFieldName).toBe('travelDistance');
});

it('supports multiple tags on the same column (string[] form)', () => {
const registry = buildMockRegistry({
counties: {
Expand Down
19 changes: 16 additions & 3 deletions graphile/graphile-postgis/src/plugins/spatial-relations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ export function collectSpatialRelations(build: any): SpatialRelationInfo[] {
const pgRegistry = build.input?.pgRegistry;
if (!pgRegistry) return [];

// Inflection is used to normalize user-supplied identifiers (the
// parametric arg name, e.g. `travel_distance` → `travelDistance`) into the
// GraphQL casing conventions. Fall back to identity if not available
// (e.g. when invoked from unit tests with a stub build).
const camelCase: (s: string) => string =
build.inflection?.camelCase?.bind(build.inflection) ?? ((s: string) => s);

const relations: SpatialRelationInfo[] = [];

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

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

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