graphile-connection-filter 1.1.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/LICENSE +23 -0
- package/README.md +107 -0
- package/augmentations.d.ts +104 -0
- package/augmentations.js +11 -0
- package/esm/augmentations.d.ts +104 -0
- package/esm/augmentations.js +9 -0
- package/esm/index.d.ts +55 -0
- package/esm/index.js +56 -0
- package/esm/plugins/ConnectionFilterArgPlugin.d.ts +13 -0
- package/esm/plugins/ConnectionFilterArgPlugin.js +96 -0
- package/esm/plugins/ConnectionFilterAttributesPlugin.d.ts +14 -0
- package/esm/plugins/ConnectionFilterAttributesPlugin.js +79 -0
- package/esm/plugins/ConnectionFilterBackwardRelationsPlugin.d.ts +33 -0
- package/esm/plugins/ConnectionFilterBackwardRelationsPlugin.js +398 -0
- package/esm/plugins/ConnectionFilterComputedAttributesPlugin.d.ts +19 -0
- package/esm/plugins/ConnectionFilterComputedAttributesPlugin.js +133 -0
- package/esm/plugins/ConnectionFilterCustomOperatorsPlugin.d.ts +35 -0
- package/esm/plugins/ConnectionFilterCustomOperatorsPlugin.js +129 -0
- package/esm/plugins/ConnectionFilterForwardRelationsPlugin.d.ts +28 -0
- package/esm/plugins/ConnectionFilterForwardRelationsPlugin.js +168 -0
- package/esm/plugins/ConnectionFilterInflectionPlugin.d.ts +11 -0
- package/esm/plugins/ConnectionFilterInflectionPlugin.js +27 -0
- package/esm/plugins/ConnectionFilterLogicalOperatorsPlugin.d.ts +15 -0
- package/esm/plugins/ConnectionFilterLogicalOperatorsPlugin.js +86 -0
- package/esm/plugins/ConnectionFilterOperatorsPlugin.d.ts +21 -0
- package/esm/plugins/ConnectionFilterOperatorsPlugin.js +677 -0
- package/esm/plugins/ConnectionFilterTypesPlugin.d.ts +12 -0
- package/esm/plugins/ConnectionFilterTypesPlugin.js +225 -0
- package/esm/plugins/index.d.ts +11 -0
- package/esm/plugins/index.js +11 -0
- package/esm/plugins/operatorApply.d.ts +11 -0
- package/esm/plugins/operatorApply.js +70 -0
- package/esm/preset.d.ts +35 -0
- package/esm/preset.js +72 -0
- package/esm/types.d.ts +146 -0
- package/esm/types.js +4 -0
- package/esm/utils.d.ts +44 -0
- package/esm/utils.js +112 -0
- package/index.d.ts +55 -0
- package/index.js +77 -0
- package/package.json +58 -0
- package/plugins/ConnectionFilterArgPlugin.d.ts +13 -0
- package/plugins/ConnectionFilterArgPlugin.js +99 -0
- package/plugins/ConnectionFilterAttributesPlugin.d.ts +14 -0
- package/plugins/ConnectionFilterAttributesPlugin.js +82 -0
- package/plugins/ConnectionFilterBackwardRelationsPlugin.d.ts +33 -0
- package/plugins/ConnectionFilterBackwardRelationsPlugin.js +401 -0
- package/plugins/ConnectionFilterComputedAttributesPlugin.d.ts +19 -0
- package/plugins/ConnectionFilterComputedAttributesPlugin.js +136 -0
- package/plugins/ConnectionFilterCustomOperatorsPlugin.d.ts +35 -0
- package/plugins/ConnectionFilterCustomOperatorsPlugin.js +132 -0
- package/plugins/ConnectionFilterForwardRelationsPlugin.d.ts +28 -0
- package/plugins/ConnectionFilterForwardRelationsPlugin.js +171 -0
- package/plugins/ConnectionFilterInflectionPlugin.d.ts +11 -0
- package/plugins/ConnectionFilterInflectionPlugin.js +30 -0
- package/plugins/ConnectionFilterLogicalOperatorsPlugin.d.ts +15 -0
- package/plugins/ConnectionFilterLogicalOperatorsPlugin.js +89 -0
- package/plugins/ConnectionFilterOperatorsPlugin.d.ts +21 -0
- package/plugins/ConnectionFilterOperatorsPlugin.js +680 -0
- package/plugins/ConnectionFilterTypesPlugin.d.ts +12 -0
- package/plugins/ConnectionFilterTypesPlugin.js +228 -0
- package/plugins/index.d.ts +11 -0
- package/plugins/index.js +25 -0
- package/plugins/operatorApply.d.ts +11 -0
- package/plugins/operatorApply.js +73 -0
- package/preset.d.ts +35 -0
- package/preset.js +75 -0
- package/types.d.ts +146 -0
- package/types.js +7 -0
- package/utils.d.ts +44 -0
- package/utils.js +119 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
3
|
+
/**
|
|
4
|
+
* ConnectionFilterCustomOperatorsPlugin
|
|
5
|
+
*
|
|
6
|
+
* Processes declarative operator factories from the preset configuration.
|
|
7
|
+
* Satellite plugins (PostGIS filter, search, pg_trgm, etc.) declare their
|
|
8
|
+
* operators via `connectionFilterOperatorFactories` in their preset's schema
|
|
9
|
+
* options. This plugin processes all factories during its own init hook,
|
|
10
|
+
* populating the filter registry used at schema build time.
|
|
11
|
+
*
|
|
12
|
+
* This declarative approach replaces the previous imperative
|
|
13
|
+
* `build.addConnectionFilterOperator()` API, eliminating timing/ordering
|
|
14
|
+
* dependencies between plugins.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // In a satellite plugin's preset:
|
|
19
|
+
* const MyPreset = {
|
|
20
|
+
* schema: {
|
|
21
|
+
* connectionFilterOperatorFactories: [
|
|
22
|
+
* (build) => [{
|
|
23
|
+
* typeNames: 'String',
|
|
24
|
+
* operatorName: 'myOp',
|
|
25
|
+
* spec: {
|
|
26
|
+
* description: 'My operator',
|
|
27
|
+
* resolve: (i, v) => build.sql`${i} OP ${v}`,
|
|
28
|
+
* },
|
|
29
|
+
* }],
|
|
30
|
+
* ],
|
|
31
|
+
* },
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare const ConnectionFilterCustomOperatorsPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import { $$filters } from '../types';
|
|
3
|
+
import { makeApplyFromOperatorSpec } from './operatorApply';
|
|
4
|
+
const version = '1.0.0';
|
|
5
|
+
/**
|
|
6
|
+
* ConnectionFilterCustomOperatorsPlugin
|
|
7
|
+
*
|
|
8
|
+
* Processes declarative operator factories from the preset configuration.
|
|
9
|
+
* Satellite plugins (PostGIS filter, search, pg_trgm, etc.) declare their
|
|
10
|
+
* operators via `connectionFilterOperatorFactories` in their preset's schema
|
|
11
|
+
* options. This plugin processes all factories during its own init hook,
|
|
12
|
+
* populating the filter registry used at schema build time.
|
|
13
|
+
*
|
|
14
|
+
* This declarative approach replaces the previous imperative
|
|
15
|
+
* `build.addConnectionFilterOperator()` API, eliminating timing/ordering
|
|
16
|
+
* dependencies between plugins.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // In a satellite plugin's preset:
|
|
21
|
+
* const MyPreset = {
|
|
22
|
+
* schema: {
|
|
23
|
+
* connectionFilterOperatorFactories: [
|
|
24
|
+
* (build) => [{
|
|
25
|
+
* typeNames: 'String',
|
|
26
|
+
* operatorName: 'myOp',
|
|
27
|
+
* spec: {
|
|
28
|
+
* description: 'My operator',
|
|
29
|
+
* resolve: (i, v) => build.sql`${i} OP ${v}`,
|
|
30
|
+
* },
|
|
31
|
+
* }],
|
|
32
|
+
* ],
|
|
33
|
+
* },
|
|
34
|
+
* };
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const ConnectionFilterCustomOperatorsPlugin = {
|
|
38
|
+
name: 'ConnectionFilterCustomOperatorsPlugin',
|
|
39
|
+
version,
|
|
40
|
+
description: 'Processes declarative operator factories for custom filter operator registration',
|
|
41
|
+
schema: {
|
|
42
|
+
hooks: {
|
|
43
|
+
build(build) {
|
|
44
|
+
// Initialize the filter registry
|
|
45
|
+
build[$$filters] = new Map();
|
|
46
|
+
return build;
|
|
47
|
+
},
|
|
48
|
+
init(_, build) {
|
|
49
|
+
const { inflection } = build;
|
|
50
|
+
const factories = build.options.connectionFilterOperatorFactories;
|
|
51
|
+
if (!factories || !Array.isArray(factories) || factories.length === 0) {
|
|
52
|
+
return _;
|
|
53
|
+
}
|
|
54
|
+
// Process each factory: call it with build, register all returned operators
|
|
55
|
+
for (const factory of factories) {
|
|
56
|
+
const registrations = factory(build);
|
|
57
|
+
if (!registrations || !Array.isArray(registrations)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
for (const registration of registrations) {
|
|
61
|
+
const { typeNames: typeNameOrNames, operatorName, spec } = registration;
|
|
62
|
+
const typeNames = Array.isArray(typeNameOrNames)
|
|
63
|
+
? typeNameOrNames
|
|
64
|
+
: [typeNameOrNames];
|
|
65
|
+
for (const typeName of typeNames) {
|
|
66
|
+
const filterTypeName = inflection.filterFieldType(typeName);
|
|
67
|
+
let operatorSpecByFilterName = build[$$filters].get(filterTypeName);
|
|
68
|
+
if (!operatorSpecByFilterName) {
|
|
69
|
+
operatorSpecByFilterName = new Map();
|
|
70
|
+
build[$$filters].set(filterTypeName, operatorSpecByFilterName);
|
|
71
|
+
}
|
|
72
|
+
if (operatorSpecByFilterName.has(operatorName)) {
|
|
73
|
+
throw new Error(`Filter '${operatorName}' already registered on '${filterTypeName}'`);
|
|
74
|
+
}
|
|
75
|
+
operatorSpecByFilterName.set(operatorName, spec);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return _;
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* Applies custom operators to their respective filter types.
|
|
83
|
+
* When a type like "StringFilter" has custom operators registered,
|
|
84
|
+
* they are added as fields with apply functions.
|
|
85
|
+
*/
|
|
86
|
+
GraphQLInputObjectType_fields(inFields, build, context) {
|
|
87
|
+
let fields = inFields;
|
|
88
|
+
const { scope: { pgConnectionFilterOperators }, Self, fieldWithHooks, } = context;
|
|
89
|
+
if (!pgConnectionFilterOperators) {
|
|
90
|
+
return fields;
|
|
91
|
+
}
|
|
92
|
+
const operatorSpecByFilterName = build[$$filters].get(Self.name);
|
|
93
|
+
if (!operatorSpecByFilterName) {
|
|
94
|
+
return fields;
|
|
95
|
+
}
|
|
96
|
+
const { inputTypeName } = pgConnectionFilterOperators;
|
|
97
|
+
const fieldInputType = build.getTypeByName(inputTypeName);
|
|
98
|
+
if (!fieldInputType) {
|
|
99
|
+
return fields;
|
|
100
|
+
}
|
|
101
|
+
for (const [filterName, spec] of operatorSpecByFilterName.entries()) {
|
|
102
|
+
const { description, resolveInputCodec, resolveType } = spec;
|
|
103
|
+
const firstCodec = pgConnectionFilterOperators.pgCodecs[0];
|
|
104
|
+
const inputCodec = resolveInputCodec
|
|
105
|
+
? resolveInputCodec(firstCodec)
|
|
106
|
+
: firstCodec;
|
|
107
|
+
const codecGraphQLType = build.getGraphQLTypeByPgCodec(inputCodec, 'input');
|
|
108
|
+
if (!codecGraphQLType) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const type = resolveType
|
|
112
|
+
? resolveType(codecGraphQLType)
|
|
113
|
+
: codecGraphQLType;
|
|
114
|
+
fields = build.extend(fields, {
|
|
115
|
+
[filterName]: fieldWithHooks({
|
|
116
|
+
fieldName: filterName,
|
|
117
|
+
isPgConnectionFilterOperator: true,
|
|
118
|
+
}, {
|
|
119
|
+
description,
|
|
120
|
+
type,
|
|
121
|
+
apply: makeApplyFromOperatorSpec(build, Self.name, filterName, spec, type),
|
|
122
|
+
}),
|
|
123
|
+
}, '');
|
|
124
|
+
}
|
|
125
|
+
return fields;
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
3
|
+
/**
|
|
4
|
+
* ConnectionFilterForwardRelationsPlugin
|
|
5
|
+
*
|
|
6
|
+
* Adds forward relation filter fields to table filter types.
|
|
7
|
+
* A "forward" relation is one where the current table has a FK referencing another table.
|
|
8
|
+
*
|
|
9
|
+
* For example, if `orders` has `client_id` referencing `clients`,
|
|
10
|
+
* then `OrderFilter` gets a `clientByClientId` field of type `ClientFilter`,
|
|
11
|
+
* allowing queries like:
|
|
12
|
+
*
|
|
13
|
+
* ```graphql
|
|
14
|
+
* allOrders(filter: {
|
|
15
|
+
* clientByClientId: { name: { startsWith: "Acme" } }
|
|
16
|
+
* }) { ... }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The SQL generated is an EXISTS subquery:
|
|
20
|
+
* ```sql
|
|
21
|
+
* WHERE EXISTS (
|
|
22
|
+
* SELECT 1 FROM clients
|
|
23
|
+
* WHERE clients.id = orders.client_id
|
|
24
|
+
* AND <nested filter conditions>
|
|
25
|
+
* )
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare const ConnectionFilterForwardRelationsPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import { makeAssertAllowed } from '../utils';
|
|
3
|
+
const version = '1.0.0';
|
|
4
|
+
/**
|
|
5
|
+
* ConnectionFilterForwardRelationsPlugin
|
|
6
|
+
*
|
|
7
|
+
* Adds forward relation filter fields to table filter types.
|
|
8
|
+
* A "forward" relation is one where the current table has a FK referencing another table.
|
|
9
|
+
*
|
|
10
|
+
* For example, if `orders` has `client_id` referencing `clients`,
|
|
11
|
+
* then `OrderFilter` gets a `clientByClientId` field of type `ClientFilter`,
|
|
12
|
+
* allowing queries like:
|
|
13
|
+
*
|
|
14
|
+
* ```graphql
|
|
15
|
+
* allOrders(filter: {
|
|
16
|
+
* clientByClientId: { name: { startsWith: "Acme" } }
|
|
17
|
+
* }) { ... }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The SQL generated is an EXISTS subquery:
|
|
21
|
+
* ```sql
|
|
22
|
+
* WHERE EXISTS (
|
|
23
|
+
* SELECT 1 FROM clients
|
|
24
|
+
* WHERE clients.id = orders.client_id
|
|
25
|
+
* AND <nested filter conditions>
|
|
26
|
+
* )
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const ConnectionFilterForwardRelationsPlugin = {
|
|
30
|
+
name: 'ConnectionFilterForwardRelationsPlugin',
|
|
31
|
+
version,
|
|
32
|
+
description: 'Adds forward relation filter fields to connection filter types',
|
|
33
|
+
inflection: {
|
|
34
|
+
add: {
|
|
35
|
+
filterForwardRelationExistsFieldName(_preset, relationFieldName) {
|
|
36
|
+
return `${relationFieldName}Exists`;
|
|
37
|
+
},
|
|
38
|
+
filterSingleRelationFieldName(_preset, fieldName) {
|
|
39
|
+
return fieldName;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
schema: {
|
|
44
|
+
entityBehavior: {
|
|
45
|
+
pgCodecRelation: 'filterBy',
|
|
46
|
+
},
|
|
47
|
+
hooks: {
|
|
48
|
+
GraphQLInputObjectType_fields(inFields, build, context) {
|
|
49
|
+
let fields = inFields;
|
|
50
|
+
// Runtime check: only proceed if relation filters are enabled
|
|
51
|
+
if (!build.options.connectionFilterRelations) {
|
|
52
|
+
return fields;
|
|
53
|
+
}
|
|
54
|
+
const { extend, inflection, sql, graphql: { GraphQLBoolean }, EXPORTABLE, } = build;
|
|
55
|
+
const { fieldWithHooks, scope: { pgCodec, isPgConnectionFilter }, } = context;
|
|
56
|
+
if (!isPgConnectionFilter || !pgCodec || !pgCodec.attributes) {
|
|
57
|
+
return fields;
|
|
58
|
+
}
|
|
59
|
+
const assertAllowed = makeAssertAllowed(build);
|
|
60
|
+
const source = Object.values(build.input.pgRegistry.pgResources).find((s) => s.codec === pgCodec && !s.parameters);
|
|
61
|
+
if (!source)
|
|
62
|
+
return fields;
|
|
63
|
+
const relations = source.getRelations();
|
|
64
|
+
const forwardRelations = Object.entries(relations).filter(([_relationName, relation]) => !relation.isReferencee);
|
|
65
|
+
const requireIndex = build.options.connectionFilterRelationsRequireIndex !== false;
|
|
66
|
+
for (const [relationName, relation] of forwardRelations) {
|
|
67
|
+
const foreignTable = relation.remoteResource;
|
|
68
|
+
if (!build.behavior.pgCodecRelationMatches(relation, 'filterBy')) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Skip function-based sources
|
|
72
|
+
if (typeof foreignTable.from === 'function') {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// Skip unindexed relations when requireIndex is enabled.
|
|
76
|
+
// PgIndexBehaviorsPlugin sets relation.extensions.isIndexed = false
|
|
77
|
+
// on backward relations without supporting indexes. For forward
|
|
78
|
+
// relations this is less common (the referenced PK is always indexed)
|
|
79
|
+
// but we check it for consistency.
|
|
80
|
+
if (requireIndex && relation.extensions?.isIndexed === false) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const fieldName = inflection.singleRelation({
|
|
84
|
+
registry: source.registry,
|
|
85
|
+
codec: source.codec,
|
|
86
|
+
relationName,
|
|
87
|
+
});
|
|
88
|
+
const filterFieldName = inflection.filterSingleRelationFieldName(fieldName);
|
|
89
|
+
const foreignTableTypeName = inflection.tableType(foreignTable.codec);
|
|
90
|
+
const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
|
|
91
|
+
const ForeignTableFilterType = build.getTypeByName(foreignTableFilterTypeName);
|
|
92
|
+
if (!ForeignTableFilterType)
|
|
93
|
+
continue;
|
|
94
|
+
const foreignTableExpression = foreignTable.from;
|
|
95
|
+
const localAttributes = relation.localAttributes;
|
|
96
|
+
const remoteAttributes = relation.remoteAttributes;
|
|
97
|
+
// Add the relation filter field (e.g. clientByClientId: ClientFilter)
|
|
98
|
+
fields = extend(fields, {
|
|
99
|
+
[filterFieldName]: fieldWithHooks({
|
|
100
|
+
fieldName: filterFieldName,
|
|
101
|
+
isPgConnectionFilterField: true,
|
|
102
|
+
}, () => ({
|
|
103
|
+
description: `Filter by the object\u2019s \`${fieldName}\` relation.`,
|
|
104
|
+
type: ForeignTableFilterType,
|
|
105
|
+
apply: EXPORTABLE((assertAllowed, foreignTable, foreignTableExpression, localAttributes, remoteAttributes, sql) => function ($where, value) {
|
|
106
|
+
assertAllowed(value, 'object');
|
|
107
|
+
if (value == null)
|
|
108
|
+
return;
|
|
109
|
+
const $subQuery = $where.existsPlan({
|
|
110
|
+
tableExpression: foreignTableExpression,
|
|
111
|
+
alias: foreignTable.name,
|
|
112
|
+
});
|
|
113
|
+
localAttributes.forEach((localAttribute, i) => {
|
|
114
|
+
const remoteAttribute = remoteAttributes[i];
|
|
115
|
+
$subQuery.where(sql `${$where.alias}.${sql.identifier(localAttribute)} = ${$subQuery.alias}.${sql.identifier(remoteAttribute)}`);
|
|
116
|
+
});
|
|
117
|
+
return $subQuery;
|
|
118
|
+
}, [
|
|
119
|
+
assertAllowed,
|
|
120
|
+
foreignTable,
|
|
121
|
+
foreignTableExpression,
|
|
122
|
+
localAttributes,
|
|
123
|
+
remoteAttributes,
|
|
124
|
+
sql,
|
|
125
|
+
]),
|
|
126
|
+
})),
|
|
127
|
+
}, `Adding connection filter forward relation field from ${source.name} to ${foreignTable.name}`);
|
|
128
|
+
// Add an "exists" field for nullable FKs (e.g. clientByClientIdExists: Boolean)
|
|
129
|
+
const keyIsNullable = relation.localAttributes.some((col) => !source.codec.attributes[col]?.notNull);
|
|
130
|
+
if (keyIsNullable) {
|
|
131
|
+
const existsFieldName = inflection.filterForwardRelationExistsFieldName(fieldName);
|
|
132
|
+
fields = extend(fields, {
|
|
133
|
+
[existsFieldName]: fieldWithHooks({
|
|
134
|
+
fieldName: existsFieldName,
|
|
135
|
+
isPgConnectionFilterField: true,
|
|
136
|
+
}, () => ({
|
|
137
|
+
description: `A related \`${fieldName}\` exists.`,
|
|
138
|
+
type: GraphQLBoolean,
|
|
139
|
+
apply: EXPORTABLE((assertAllowed, foreignTable, foreignTableExpression, localAttributes, remoteAttributes, sql) => function ($where, value) {
|
|
140
|
+
assertAllowed(value, 'scalar');
|
|
141
|
+
if (value == null)
|
|
142
|
+
return;
|
|
143
|
+
const $subQuery = $where.existsPlan({
|
|
144
|
+
tableExpression: foreignTableExpression,
|
|
145
|
+
alias: foreignTable.name,
|
|
146
|
+
equals: value,
|
|
147
|
+
});
|
|
148
|
+
localAttributes.forEach((localAttribute, i) => {
|
|
149
|
+
const remoteAttribute = remoteAttributes[i];
|
|
150
|
+
$subQuery.where(sql `${$where.alias}.${sql.identifier(localAttribute)} = ${$subQuery.alias}.${sql.identifier(remoteAttribute)}`);
|
|
151
|
+
});
|
|
152
|
+
}, [
|
|
153
|
+
assertAllowed,
|
|
154
|
+
foreignTable,
|
|
155
|
+
foreignTableExpression,
|
|
156
|
+
localAttributes,
|
|
157
|
+
remoteAttributes,
|
|
158
|
+
sql,
|
|
159
|
+
]),
|
|
160
|
+
})),
|
|
161
|
+
}, `Adding connection filter forward relation exists field for ${fieldName}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return fields;
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
3
|
+
/**
|
|
4
|
+
* ConnectionFilterInflectionPlugin
|
|
5
|
+
*
|
|
6
|
+
* Adds inflection methods for naming filter types:
|
|
7
|
+
* - filterType(typeName) -> e.g. "UserFilter"
|
|
8
|
+
* - filterFieldType(typeName) -> e.g. "StringFilter"
|
|
9
|
+
* - filterFieldListType(typeName) -> e.g. "StringListFilter"
|
|
10
|
+
*/
|
|
11
|
+
export declare const ConnectionFilterInflectionPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
/**
|
|
3
|
+
* ConnectionFilterInflectionPlugin
|
|
4
|
+
*
|
|
5
|
+
* Adds inflection methods for naming filter types:
|
|
6
|
+
* - filterType(typeName) -> e.g. "UserFilter"
|
|
7
|
+
* - filterFieldType(typeName) -> e.g. "StringFilter"
|
|
8
|
+
* - filterFieldListType(typeName) -> e.g. "StringListFilter"
|
|
9
|
+
*/
|
|
10
|
+
export const ConnectionFilterInflectionPlugin = {
|
|
11
|
+
name: 'ConnectionFilterInflectionPlugin',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
description: 'Adds inflection methods for connection filter type naming',
|
|
14
|
+
inflection: {
|
|
15
|
+
add: {
|
|
16
|
+
filterType(_preset, typeName) {
|
|
17
|
+
return `${typeName}Filter`;
|
|
18
|
+
},
|
|
19
|
+
filterFieldType(_preset, typeName) {
|
|
20
|
+
return `${typeName}Filter`;
|
|
21
|
+
},
|
|
22
|
+
filterFieldListType(_preset, typeName) {
|
|
23
|
+
return `${typeName}ListFilter`;
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
3
|
+
/**
|
|
4
|
+
* ConnectionFilterLogicalOperatorsPlugin
|
|
5
|
+
*
|
|
6
|
+
* Adds the `and`, `or`, and `not` logical operators to filter types.
|
|
7
|
+
*
|
|
8
|
+
* - `and`: [Filter!] - all conditions must match (uses $where.andPlan())
|
|
9
|
+
* - `or`: [Filter!] - any condition must match (uses $where.orPlan())
|
|
10
|
+
* - `not`: Filter - negates the condition (uses $where.notPlan())
|
|
11
|
+
*
|
|
12
|
+
* These are only added if the filter type has at least one other field
|
|
13
|
+
* (to avoid creating useless logical-only filter types).
|
|
14
|
+
*/
|
|
15
|
+
export declare const ConnectionFilterLogicalOperatorsPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import { makeAssertAllowed } from '../utils';
|
|
3
|
+
const version = '1.0.0';
|
|
4
|
+
/**
|
|
5
|
+
* ConnectionFilterLogicalOperatorsPlugin
|
|
6
|
+
*
|
|
7
|
+
* Adds the `and`, `or`, and `not` logical operators to filter types.
|
|
8
|
+
*
|
|
9
|
+
* - `and`: [Filter!] - all conditions must match (uses $where.andPlan())
|
|
10
|
+
* - `or`: [Filter!] - any condition must match (uses $where.orPlan())
|
|
11
|
+
* - `not`: Filter - negates the condition (uses $where.notPlan())
|
|
12
|
+
*
|
|
13
|
+
* These are only added if the filter type has at least one other field
|
|
14
|
+
* (to avoid creating useless logical-only filter types).
|
|
15
|
+
*/
|
|
16
|
+
export const ConnectionFilterLogicalOperatorsPlugin = {
|
|
17
|
+
name: 'ConnectionFilterLogicalOperatorsPlugin',
|
|
18
|
+
version,
|
|
19
|
+
description: 'Adds and/or/not logical operators to connection filter types',
|
|
20
|
+
schema: {
|
|
21
|
+
hooks: {
|
|
22
|
+
GraphQLInputObjectType_fields(fields, build, context) {
|
|
23
|
+
const { extend, graphql: { GraphQLList, GraphQLNonNull }, EXPORTABLE, } = build;
|
|
24
|
+
const { fieldWithHooks, scope: { isPgConnectionFilter }, Self, } = context;
|
|
25
|
+
if (!isPgConnectionFilter)
|
|
26
|
+
return fields;
|
|
27
|
+
// Check runtime option — allows toggling without removing the plugin
|
|
28
|
+
if (build.options.connectionFilterLogicalOperators === false) {
|
|
29
|
+
return fields;
|
|
30
|
+
}
|
|
31
|
+
// Don't add logical operators if there are no other fields
|
|
32
|
+
if (Object.keys(fields).length === 0) {
|
|
33
|
+
return fields;
|
|
34
|
+
}
|
|
35
|
+
const assertAllowed = makeAssertAllowed(build);
|
|
36
|
+
const logicalOperatorFields = {
|
|
37
|
+
and: fieldWithHooks({
|
|
38
|
+
fieldName: 'and',
|
|
39
|
+
isPgConnectionFilterOperatorLogical: true,
|
|
40
|
+
}, {
|
|
41
|
+
description: 'Checks for all expressions in this list.',
|
|
42
|
+
type: new GraphQLList(new GraphQLNonNull(Self)),
|
|
43
|
+
apply: EXPORTABLE((assertAllowed) => function ($where, value) {
|
|
44
|
+
assertAllowed(value, 'list');
|
|
45
|
+
if (value == null)
|
|
46
|
+
return;
|
|
47
|
+
const $and = $where.andPlan();
|
|
48
|
+
return $and;
|
|
49
|
+
}, [assertAllowed]),
|
|
50
|
+
}),
|
|
51
|
+
or: fieldWithHooks({
|
|
52
|
+
fieldName: 'or',
|
|
53
|
+
isPgConnectionFilterOperatorLogical: true,
|
|
54
|
+
}, {
|
|
55
|
+
description: 'Checks for any expressions in this list.',
|
|
56
|
+
type: new GraphQLList(new GraphQLNonNull(Self)),
|
|
57
|
+
apply: EXPORTABLE((assertAllowed) => function ($where, value) {
|
|
58
|
+
assertAllowed(value, 'list');
|
|
59
|
+
if (value == null)
|
|
60
|
+
return;
|
|
61
|
+
const $or = $where.orPlan();
|
|
62
|
+
// Each entry in the OR list should use AND internally
|
|
63
|
+
return () => $or.andPlan();
|
|
64
|
+
}, [assertAllowed]),
|
|
65
|
+
}),
|
|
66
|
+
not: fieldWithHooks({
|
|
67
|
+
fieldName: 'not',
|
|
68
|
+
isPgConnectionFilterOperatorLogical: true,
|
|
69
|
+
}, {
|
|
70
|
+
description: 'Negates the expression.',
|
|
71
|
+
type: Self,
|
|
72
|
+
apply: EXPORTABLE((assertAllowed) => function ($where, value) {
|
|
73
|
+
assertAllowed(value, 'object');
|
|
74
|
+
if (value == null)
|
|
75
|
+
return;
|
|
76
|
+
const $not = $where.notPlan();
|
|
77
|
+
const $and = $not.andPlan();
|
|
78
|
+
return $and;
|
|
79
|
+
}, [assertAllowed]),
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
return extend(fields, logicalOperatorFields, '');
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import '../augmentations';
|
|
2
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
3
|
+
/**
|
|
4
|
+
* ConnectionFilterOperatorsPlugin
|
|
5
|
+
*
|
|
6
|
+
* Registers all built-in filter operators on the per-scalar operator types
|
|
7
|
+
* (e.g. StringFilter, IntFilter, DatetimeFilter, etc.).
|
|
8
|
+
*
|
|
9
|
+
* Operator categories:
|
|
10
|
+
* - Standard: isNull, equalTo, notEqualTo, distinctFrom, notDistinctFrom, in, notIn
|
|
11
|
+
* - Sort: lessThan, lessThanOrEqualTo, greaterThan, greaterThanOrEqualTo
|
|
12
|
+
* - Pattern matching (text-like): includes, startsWith, endsWith, like + insensitive variants
|
|
13
|
+
* - Hstore: contains, containsKey, containsAllKeys, containsAnyKeys, containedBy
|
|
14
|
+
* - JSONB: contains, containsKey, containsAllKeys, containsAnyKeys, containedBy
|
|
15
|
+
* - Inet: contains, containsOrEqualTo, containedBy, containedByOrEqualTo, containsOrContainedBy
|
|
16
|
+
* - Array: contains, containedBy, overlaps, anyEqualTo, anyNotEqualTo, any comparison operators
|
|
17
|
+
* - Range: contains, containsElement, containedBy, overlaps, strictlyLeftOf, strictlyRightOf, etc.
|
|
18
|
+
* - Enum: standard + sort operators
|
|
19
|
+
* - Case-insensitive variants of standard and sort operators
|
|
20
|
+
*/
|
|
21
|
+
export declare const ConnectionFilterOperatorsPlugin: GraphileConfig.Plugin;
|