graphile-plugin-connection-filter 2.3.1
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/ConnectionArgFilterPlugin.d.ts +3 -0
- package/ConnectionArgFilterPlugin.js +18 -0
- package/LICENSE +23 -0
- package/PgConnectionArgFilterBackwardRelationsPlugin.d.ts +12 -0
- package/PgConnectionArgFilterBackwardRelationsPlugin.js +244 -0
- package/PgConnectionArgFilterColumnsPlugin.d.ts +3 -0
- package/PgConnectionArgFilterColumnsPlugin.js +51 -0
- package/PgConnectionArgFilterCompositeTypeColumnsPlugin.d.ts +3 -0
- package/PgConnectionArgFilterCompositeTypeColumnsPlugin.js +67 -0
- package/PgConnectionArgFilterComputedColumnsPlugin.d.ts +3 -0
- package/PgConnectionArgFilterComputedColumnsPlugin.js +114 -0
- package/PgConnectionArgFilterForwardRelationsPlugin.d.ts +11 -0
- package/PgConnectionArgFilterForwardRelationsPlugin.js +130 -0
- package/PgConnectionArgFilterLogicalOperatorsPlugin.d.ts +3 -0
- package/PgConnectionArgFilterLogicalOperatorsPlugin.js +67 -0
- package/PgConnectionArgFilterOperatorsPlugin.d.ts +15 -0
- package/PgConnectionArgFilterOperatorsPlugin.js +551 -0
- package/PgConnectionArgFilterPlugin.d.ts +27 -0
- package/PgConnectionArgFilterPlugin.js +305 -0
- package/PgConnectionArgFilterRecordFunctionsPlugin.d.ts +3 -0
- package/PgConnectionArgFilterRecordFunctionsPlugin.js +75 -0
- package/README.md +364 -0
- package/esm/ConnectionArgFilterPlugin.js +16 -0
- package/esm/PgConnectionArgFilterBackwardRelationsPlugin.js +242 -0
- package/esm/PgConnectionArgFilterColumnsPlugin.js +49 -0
- package/esm/PgConnectionArgFilterCompositeTypeColumnsPlugin.js +65 -0
- package/esm/PgConnectionArgFilterComputedColumnsPlugin.js +112 -0
- package/esm/PgConnectionArgFilterForwardRelationsPlugin.js +128 -0
- package/esm/PgConnectionArgFilterLogicalOperatorsPlugin.js +65 -0
- package/esm/PgConnectionArgFilterOperatorsPlugin.js +549 -0
- package/esm/PgConnectionArgFilterPlugin.js +303 -0
- package/esm/PgConnectionArgFilterRecordFunctionsPlugin.js +73 -0
- package/esm/index.js +58 -0
- package/esm/types.js +1 -0
- package/index.d.ts +6 -0
- package/index.js +64 -0
- package/package.json +58 -0
- package/types.d.ts +25 -0
- package/types.js +2 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
const PgConnectionArgFilterPlugin = (builder, rawOptions) => {
|
|
2
|
+
const { connectionFilterAllowedFieldTypes, connectionFilterArrays, connectionFilterSetofFunctions, connectionFilterAllowNullInput, connectionFilterAllowEmptyObjectInput, } = rawOptions;
|
|
3
|
+
// Add `filter` input argument to connection and simple collection types
|
|
4
|
+
builder.hook('GraphQLObjectType:fields:field:args', (args, build, context) => {
|
|
5
|
+
const { extend, newWithHooks, getTypeByName, inflection, pgGetGqlTypeByTypeIdAndModifier, pgOmit: omit, connectionFilterResolve, connectionFilterType, } = build;
|
|
6
|
+
const { scope: { isPgFieldConnection, isPgFieldSimpleCollection, pgFieldIntrospection: source, }, addArgDataGenerator, field, Self, } = context;
|
|
7
|
+
const shouldAddFilter = isPgFieldConnection || isPgFieldSimpleCollection;
|
|
8
|
+
if (!shouldAddFilter)
|
|
9
|
+
return args;
|
|
10
|
+
if (!source)
|
|
11
|
+
return args;
|
|
12
|
+
if (omit(source, 'filter'))
|
|
13
|
+
return args;
|
|
14
|
+
if (source.kind === 'procedure') {
|
|
15
|
+
if (!(source.tags.filterable || connectionFilterSetofFunctions)) {
|
|
16
|
+
return args;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const returnTypeId = source.kind === 'class' ? source.type.id : source.returnTypeId;
|
|
20
|
+
const returnType = source.kind === 'class'
|
|
21
|
+
? source.type
|
|
22
|
+
: build.pgIntrospectionResultsByKind.type.find((t) => t.id === returnTypeId);
|
|
23
|
+
if (!returnType) {
|
|
24
|
+
return args;
|
|
25
|
+
}
|
|
26
|
+
const isRecordLike = returnTypeId === '2249';
|
|
27
|
+
const nodeTypeName = isRecordLike
|
|
28
|
+
? inflection.recordFunctionReturnType(source)
|
|
29
|
+
: pgGetGqlTypeByTypeIdAndModifier(returnTypeId, null).name;
|
|
30
|
+
const filterTypeName = inflection.filterType(nodeTypeName);
|
|
31
|
+
const nodeType = getTypeByName(nodeTypeName);
|
|
32
|
+
if (!nodeType) {
|
|
33
|
+
return args;
|
|
34
|
+
}
|
|
35
|
+
const nodeSource = source.kind === 'procedure' && returnType.class
|
|
36
|
+
? returnType.class
|
|
37
|
+
: source;
|
|
38
|
+
const FilterType = connectionFilterType(newWithHooks, filterTypeName, nodeSource, nodeTypeName);
|
|
39
|
+
if (!FilterType) {
|
|
40
|
+
return args;
|
|
41
|
+
}
|
|
42
|
+
// Generate SQL where clause from filter argument
|
|
43
|
+
addArgDataGenerator(function connectionFilter(args) {
|
|
44
|
+
return {
|
|
45
|
+
pgQuery: (queryBuilder) => {
|
|
46
|
+
if (Object.prototype.hasOwnProperty.call(args, 'filter')) {
|
|
47
|
+
const sqlFragment = connectionFilterResolve(args.filter, queryBuilder.getTableAlias(), filterTypeName, queryBuilder, returnType, null);
|
|
48
|
+
if (sqlFragment != null) {
|
|
49
|
+
queryBuilder.where(sqlFragment);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
return extend(args, {
|
|
56
|
+
filter: {
|
|
57
|
+
description: 'A filter to be used in determining which values should be returned by the collection.',
|
|
58
|
+
type: FilterType,
|
|
59
|
+
},
|
|
60
|
+
}, `Adding connection filter arg to field '${field.name}' of '${Self.name}'`);
|
|
61
|
+
});
|
|
62
|
+
builder.hook('build', (build) => {
|
|
63
|
+
const { extend, graphql: { getNamedType, GraphQLInputObjectType, GraphQLList }, inflection, pgIntrospectionResultsByKind: introspectionResultsByKind, pgGetGqlInputTypeByTypeIdAndModifier, pgGetGqlTypeByTypeIdAndModifier, pgSql: sql, } = build;
|
|
64
|
+
const connectionFilterResolvers = {};
|
|
65
|
+
const connectionFilterTypesByTypeName = {};
|
|
66
|
+
const handleNullInput = () => {
|
|
67
|
+
if (!connectionFilterAllowNullInput) {
|
|
68
|
+
throw new Error('Null literals are forbidden in filter argument input.');
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
const handleEmptyObjectInput = () => {
|
|
73
|
+
if (!connectionFilterAllowEmptyObjectInput) {
|
|
74
|
+
throw new Error('Empty objects are forbidden in filter argument input.');
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
const isEmptyObject = (obj) => typeof obj === 'object' &&
|
|
79
|
+
obj !== null &&
|
|
80
|
+
!Array.isArray(obj) &&
|
|
81
|
+
Object.keys(obj).length === 0;
|
|
82
|
+
const connectionFilterRegisterResolver = (typeName, fieldName, resolve) => {
|
|
83
|
+
const existingResolvers = connectionFilterResolvers[typeName] || {};
|
|
84
|
+
if (existingResolvers[fieldName]) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
connectionFilterResolvers[typeName] = extend(existingResolvers, {
|
|
88
|
+
[fieldName]: resolve,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
const connectionFilterResolve = (obj, sourceAlias, typeName, queryBuilder, pgType, pgTypeModifier, parentFieldName, parentFieldInfo) => {
|
|
92
|
+
if (obj == null)
|
|
93
|
+
return handleNullInput();
|
|
94
|
+
if (isEmptyObject(obj))
|
|
95
|
+
return handleEmptyObjectInput();
|
|
96
|
+
const sqlFragments = Object.entries(obj)
|
|
97
|
+
.map(([key, value]) => {
|
|
98
|
+
if (value == null)
|
|
99
|
+
return handleNullInput();
|
|
100
|
+
if (isEmptyObject(value))
|
|
101
|
+
return handleEmptyObjectInput();
|
|
102
|
+
const resolversByFieldName = connectionFilterResolvers[typeName];
|
|
103
|
+
if (resolversByFieldName && resolversByFieldName[key]) {
|
|
104
|
+
return resolversByFieldName[key]({
|
|
105
|
+
sourceAlias,
|
|
106
|
+
fieldName: key,
|
|
107
|
+
fieldValue: value,
|
|
108
|
+
queryBuilder,
|
|
109
|
+
pgType,
|
|
110
|
+
pgTypeModifier,
|
|
111
|
+
parentFieldName,
|
|
112
|
+
parentFieldInfo,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Unable to resolve filter field '${key}'`);
|
|
116
|
+
})
|
|
117
|
+
.filter((x) => x != null);
|
|
118
|
+
return sqlFragments.length === 0
|
|
119
|
+
? null
|
|
120
|
+
: sql.query `(${sql.join(sqlFragments, ') and (')})`;
|
|
121
|
+
};
|
|
122
|
+
// Get or create types like IntFilter, StringFilter, etc.
|
|
123
|
+
const connectionFilterOperatorsType = (newWithHooks, pgTypeId, pgTypeModifier) => {
|
|
124
|
+
const pgType = introspectionResultsByKind.typeById[pgTypeId];
|
|
125
|
+
const allowedPgTypeTypes = ['b', 'd', 'e', 'r'];
|
|
126
|
+
if (!allowedPgTypeTypes.includes(pgType.type)) {
|
|
127
|
+
// Not a base, domain, enum, or range type? Skip.
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
// Perform some checks on the simple type (after removing array/range/domain wrappers)
|
|
131
|
+
const pgGetNonArrayType = (pgType) => pgType.isPgArray && pgType.arrayItemType
|
|
132
|
+
? pgType.arrayItemType
|
|
133
|
+
: pgType;
|
|
134
|
+
const pgGetNonRangeType = (pgType) => pgType.rangeSubTypeId
|
|
135
|
+
? introspectionResultsByKind.typeById[pgType.rangeSubTypeId]
|
|
136
|
+
: pgType;
|
|
137
|
+
const pgGetNonDomainType = (pgType) => pgType.type === 'd' && pgType.domainBaseTypeId
|
|
138
|
+
? introspectionResultsByKind.typeById[pgType.domainBaseTypeId]
|
|
139
|
+
: pgType;
|
|
140
|
+
const pgGetSimpleType = (pgType) => pgGetNonDomainType(pgGetNonRangeType(pgGetNonArrayType(pgType)));
|
|
141
|
+
const pgSimpleType = pgGetSimpleType(pgType);
|
|
142
|
+
if (!pgSimpleType)
|
|
143
|
+
return null;
|
|
144
|
+
if (!(pgSimpleType.type === 'e' ||
|
|
145
|
+
(pgSimpleType.type === 'b' && !pgSimpleType.isPgArray))) {
|
|
146
|
+
// Haven't found an enum type or a non-array base type? Skip.
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
if (pgSimpleType.name === 'json') {
|
|
150
|
+
// The PG `json` type has no valid operators.
|
|
151
|
+
// Skip filter type creation to allow the proper
|
|
152
|
+
// operators to be exposed for PG `jsonb` types.
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
// Establish field type and field input type
|
|
156
|
+
const fieldType = pgGetGqlTypeByTypeIdAndModifier(pgTypeId, pgTypeModifier);
|
|
157
|
+
if (!fieldType)
|
|
158
|
+
return null;
|
|
159
|
+
const fieldInputType = pgGetGqlInputTypeByTypeIdAndModifier(pgTypeId, pgTypeModifier);
|
|
160
|
+
if (!fieldInputType)
|
|
161
|
+
return null;
|
|
162
|
+
// Avoid exposing filter operators on unrecognized types that PostGraphile handles as Strings
|
|
163
|
+
const namedType = getNamedType(fieldType);
|
|
164
|
+
const namedInputType = getNamedType(fieldInputType);
|
|
165
|
+
const actualStringPgTypeIds = [
|
|
166
|
+
'1042', // bpchar
|
|
167
|
+
'18', // char
|
|
168
|
+
'19', // name
|
|
169
|
+
'25', // text
|
|
170
|
+
'1043', // varchar
|
|
171
|
+
];
|
|
172
|
+
// Include citext as recognized String type
|
|
173
|
+
const citextPgType = introspectionResultsByKind.type.find((t) => t.name === 'citext');
|
|
174
|
+
if (citextPgType) {
|
|
175
|
+
actualStringPgTypeIds.push(citextPgType.id);
|
|
176
|
+
}
|
|
177
|
+
if (namedInputType &&
|
|
178
|
+
namedInputType.name === 'String' &&
|
|
179
|
+
!actualStringPgTypeIds.includes(pgSimpleType.id)) {
|
|
180
|
+
// Not a real string type? Skip.
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
// Respect `connectionFilterAllowedFieldTypes` config option
|
|
184
|
+
if (connectionFilterAllowedFieldTypes &&
|
|
185
|
+
!connectionFilterAllowedFieldTypes.includes(namedType.name)) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const pgConnectionFilterOperatorsCategory = pgType.isPgArray
|
|
189
|
+
? 'Array'
|
|
190
|
+
: pgType.rangeSubTypeId
|
|
191
|
+
? 'Range'
|
|
192
|
+
: pgType.type === 'e'
|
|
193
|
+
? 'Enum'
|
|
194
|
+
: pgType.type === 'd'
|
|
195
|
+
? 'Domain'
|
|
196
|
+
: 'Scalar';
|
|
197
|
+
// Respect `connectionFilterArrays` config option
|
|
198
|
+
if (pgConnectionFilterOperatorsCategory === 'Array' &&
|
|
199
|
+
!connectionFilterArrays) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const rangeElementInputType = pgType.rangeSubTypeId
|
|
203
|
+
? pgGetGqlInputTypeByTypeIdAndModifier(pgType.rangeSubTypeId, pgTypeModifier)
|
|
204
|
+
: null;
|
|
205
|
+
const domainBaseType = pgType.type === 'd'
|
|
206
|
+
? pgGetGqlTypeByTypeIdAndModifier(pgType.domainBaseTypeId, pgType.domainTypeModifier)
|
|
207
|
+
: null;
|
|
208
|
+
const isListType = fieldType instanceof GraphQLList;
|
|
209
|
+
const operatorsTypeName = isListType
|
|
210
|
+
? inflection.filterFieldListType(namedType.name)
|
|
211
|
+
: inflection.filterFieldType(namedType.name);
|
|
212
|
+
const existingType = connectionFilterTypesByTypeName[operatorsTypeName];
|
|
213
|
+
if (existingType) {
|
|
214
|
+
return existingType;
|
|
215
|
+
}
|
|
216
|
+
return newWithHooks(GraphQLInputObjectType, {
|
|
217
|
+
name: operatorsTypeName,
|
|
218
|
+
description: `A filter to be used against ${namedType.name}${isListType ? ' List' : ''} fields. All fields are combined with a logical ‘and.’`,
|
|
219
|
+
}, {
|
|
220
|
+
isPgConnectionFilterOperators: true,
|
|
221
|
+
pgConnectionFilterOperatorsCategory,
|
|
222
|
+
fieldType,
|
|
223
|
+
fieldInputType,
|
|
224
|
+
rangeElementInputType,
|
|
225
|
+
domainBaseType,
|
|
226
|
+
}, true);
|
|
227
|
+
};
|
|
228
|
+
const connectionFilterType = (newWithHooks, filterTypeName, source, nodeTypeName) => {
|
|
229
|
+
const existingType = connectionFilterTypesByTypeName[filterTypeName];
|
|
230
|
+
if (existingType) {
|
|
231
|
+
return existingType;
|
|
232
|
+
}
|
|
233
|
+
const placeholder = new GraphQLInputObjectType({
|
|
234
|
+
name: filterTypeName,
|
|
235
|
+
fields: {},
|
|
236
|
+
});
|
|
237
|
+
connectionFilterTypesByTypeName[filterTypeName] = placeholder;
|
|
238
|
+
const FilterType = newWithHooks(GraphQLInputObjectType, {
|
|
239
|
+
description: `A filter to be used against \`${nodeTypeName}\` object types. All fields are combined with a logical ‘and.’`,
|
|
240
|
+
name: filterTypeName,
|
|
241
|
+
}, {
|
|
242
|
+
pgIntrospection: source,
|
|
243
|
+
isPgConnectionFilter: true,
|
|
244
|
+
}, true);
|
|
245
|
+
connectionFilterTypesByTypeName[filterTypeName] = FilterType;
|
|
246
|
+
return FilterType;
|
|
247
|
+
};
|
|
248
|
+
const escapeLikeWildcards = (input) => {
|
|
249
|
+
if ('string' !== typeof input) {
|
|
250
|
+
throw new Error('Non-string input was provided to escapeLikeWildcards');
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
return input.split('%').join('\\%').split('_').join('\\_');
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const addConnectionFilterOperator = (typeNames, operatorName, description, resolveType, resolve, options = {}) => {
|
|
257
|
+
if (!typeNames) {
|
|
258
|
+
const msg = `Missing first argument 'typeNames' in call to 'addConnectionFilterOperator' for operator '${operatorName}'`;
|
|
259
|
+
throw new Error(msg);
|
|
260
|
+
}
|
|
261
|
+
if (!operatorName) {
|
|
262
|
+
const msg = `Missing second argument 'operatorName' in call to 'addConnectionFilterOperator' for operator '${operatorName}'`;
|
|
263
|
+
throw new Error(msg);
|
|
264
|
+
}
|
|
265
|
+
if (!resolveType) {
|
|
266
|
+
const msg = `Missing fourth argument 'resolveType' in call to 'addConnectionFilterOperator' for operator '${operatorName}'`;
|
|
267
|
+
throw new Error(msg);
|
|
268
|
+
}
|
|
269
|
+
if (!resolve) {
|
|
270
|
+
const msg = `Missing fifth argument 'resolve' in call to 'addConnectionFilterOperator' for operator '${operatorName}'`;
|
|
271
|
+
throw new Error(msg);
|
|
272
|
+
}
|
|
273
|
+
const { connectionFilterScalarOperators } = build;
|
|
274
|
+
const gqlTypeNames = Array.isArray(typeNames) ? typeNames : [typeNames];
|
|
275
|
+
for (const gqlTypeName of gqlTypeNames) {
|
|
276
|
+
if (!connectionFilterScalarOperators[gqlTypeName]) {
|
|
277
|
+
connectionFilterScalarOperators[gqlTypeName] = {};
|
|
278
|
+
}
|
|
279
|
+
if (connectionFilterScalarOperators[gqlTypeName][operatorName]) {
|
|
280
|
+
const msg = `Operator '${operatorName}' already exists for type '${gqlTypeName}'.`;
|
|
281
|
+
throw new Error(msg);
|
|
282
|
+
}
|
|
283
|
+
connectionFilterScalarOperators[gqlTypeName][operatorName] = {
|
|
284
|
+
description,
|
|
285
|
+
resolveType,
|
|
286
|
+
resolve,
|
|
287
|
+
// These functions may exist on `options`: resolveSqlIdentifier, resolveSqlValue, resolveInput
|
|
288
|
+
...options,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
return extend(build, {
|
|
293
|
+
connectionFilterTypesByTypeName,
|
|
294
|
+
connectionFilterRegisterResolver,
|
|
295
|
+
connectionFilterResolve,
|
|
296
|
+
connectionFilterOperatorsType,
|
|
297
|
+
connectionFilterType,
|
|
298
|
+
escapeLikeWildcards,
|
|
299
|
+
addConnectionFilterOperator,
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
export default PgConnectionArgFilterPlugin;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const PgConnectionArgFilterRecordFunctionsPlugin = (builder, rawOptions) => {
|
|
2
|
+
const { connectionFilterSetofFunctions } = rawOptions;
|
|
3
|
+
builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
|
|
4
|
+
const { extend, newWithHooks, pgSql: sql, pgIntrospectionResultsByKind: introspectionResultsByKind, pgGetGqlTypeByTypeIdAndModifier, inflection, describePgEntity, connectionFilterOperatorsType, connectionFilterRegisterResolver, connectionFilterResolve, connectionFilterTypesByTypeName, } = build;
|
|
5
|
+
const { fieldWithHooks, scope: { pgIntrospection: proc, isPgConnectionFilter }, Self, } = context;
|
|
6
|
+
if (!isPgConnectionFilter || proc.kind !== 'procedure')
|
|
7
|
+
return fields;
|
|
8
|
+
connectionFilterTypesByTypeName[Self.name] = Self;
|
|
9
|
+
// Must return a `RECORD` type
|
|
10
|
+
const isRecordLike = proc.returnTypeId === '2249';
|
|
11
|
+
if (!isRecordLike)
|
|
12
|
+
return fields;
|
|
13
|
+
// Must be marked @filterable OR enabled via plugin option
|
|
14
|
+
if (!(proc.tags.filterable || connectionFilterSetofFunctions))
|
|
15
|
+
return fields;
|
|
16
|
+
const argModesWithOutput = [
|
|
17
|
+
'o', // OUT,
|
|
18
|
+
'b', // INOUT
|
|
19
|
+
't', // TABLE
|
|
20
|
+
];
|
|
21
|
+
const outputArgNames = proc.argTypeIds.reduce((prev, _, idx) => {
|
|
22
|
+
if (argModesWithOutput.includes(proc.argModes[idx])) {
|
|
23
|
+
prev.push(proc.argNames[idx] || '');
|
|
24
|
+
}
|
|
25
|
+
return prev;
|
|
26
|
+
}, []);
|
|
27
|
+
const outputArgTypes = proc.argTypeIds.reduce((prev, typeId, idx) => {
|
|
28
|
+
if (argModesWithOutput.includes(proc.argModes[idx])) {
|
|
29
|
+
prev.push(introspectionResultsByKind.typeById[typeId]);
|
|
30
|
+
}
|
|
31
|
+
return prev;
|
|
32
|
+
}, []);
|
|
33
|
+
const outputArgByFieldName = outputArgNames.reduce((memo, outputArgName, idx) => {
|
|
34
|
+
const fieldName = inflection.functionOutputFieldName(proc, outputArgName, idx + 1);
|
|
35
|
+
if (memo[fieldName]) {
|
|
36
|
+
throw new Error(`Tried to register field name '${fieldName}' twice in '${describePgEntity(proc)}'; the argument names are too similar.`);
|
|
37
|
+
}
|
|
38
|
+
memo[fieldName] = {
|
|
39
|
+
name: outputArgName,
|
|
40
|
+
type: outputArgTypes[idx],
|
|
41
|
+
};
|
|
42
|
+
return memo;
|
|
43
|
+
}, {});
|
|
44
|
+
const outputArgFields = Object.entries(outputArgByFieldName).reduce((memo, [fieldName, outputArg]) => {
|
|
45
|
+
const OperatorsType = connectionFilterOperatorsType(newWithHooks, outputArg.type.id, null);
|
|
46
|
+
if (!OperatorsType) {
|
|
47
|
+
return memo;
|
|
48
|
+
}
|
|
49
|
+
return extend(memo, {
|
|
50
|
+
[fieldName]: fieldWithHooks(fieldName, {
|
|
51
|
+
description: `Filter by the object’s \`${fieldName}\` field.`,
|
|
52
|
+
type: OperatorsType,
|
|
53
|
+
}, {
|
|
54
|
+
isPgConnectionFilterField: true,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
}, {});
|
|
58
|
+
const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
|
|
59
|
+
if (fieldValue == null)
|
|
60
|
+
return null;
|
|
61
|
+
const outputArg = outputArgByFieldName[fieldName];
|
|
62
|
+
const sqlIdentifier = sql.query `${sourceAlias}.${sql.identifier(outputArg.name)}`;
|
|
63
|
+
const typeName = pgGetGqlTypeByTypeIdAndModifier(outputArg.type.id, null).name;
|
|
64
|
+
const filterTypeName = inflection.filterType(typeName);
|
|
65
|
+
return connectionFilterResolve(fieldValue, sqlIdentifier, filterTypeName, queryBuilder, outputArg.type, null, fieldName);
|
|
66
|
+
};
|
|
67
|
+
for (const fieldName of Object.keys(outputArgFields)) {
|
|
68
|
+
connectionFilterRegisterResolver(Self.name, fieldName, resolve);
|
|
69
|
+
}
|
|
70
|
+
return extend(fields, outputArgFields);
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
export default PgConnectionArgFilterRecordFunctionsPlugin;
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import ConnectionArgFilterPlugin from './ConnectionArgFilterPlugin';
|
|
2
|
+
import PgConnectionArgFilterBackwardRelationsPlugin from './PgConnectionArgFilterBackwardRelationsPlugin';
|
|
3
|
+
import PgConnectionArgFilterColumnsPlugin from './PgConnectionArgFilterColumnsPlugin';
|
|
4
|
+
import PgConnectionArgFilterComputedColumnsPlugin from './PgConnectionArgFilterComputedColumnsPlugin';
|
|
5
|
+
import PgConnectionArgFilterCompositeTypeColumnsPlugin from './PgConnectionArgFilterCompositeTypeColumnsPlugin';
|
|
6
|
+
import PgConnectionArgFilterForwardRelationsPlugin from './PgConnectionArgFilterForwardRelationsPlugin';
|
|
7
|
+
import PgConnectionArgFilterLogicalOperatorsPlugin from './PgConnectionArgFilterLogicalOperatorsPlugin';
|
|
8
|
+
import PgConnectionArgFilterOperatorsPlugin from './PgConnectionArgFilterOperatorsPlugin';
|
|
9
|
+
import PgConnectionArgFilterPlugin from './PgConnectionArgFilterPlugin';
|
|
10
|
+
import PgConnectionArgFilterRecordFunctionsPlugin from './PgConnectionArgFilterRecordFunctionsPlugin';
|
|
11
|
+
import pkg from '../package.json';
|
|
12
|
+
const defaultOptions = {
|
|
13
|
+
connectionFilterArrays: true,
|
|
14
|
+
connectionFilterComputedColumns: true,
|
|
15
|
+
connectionFilterRelations: false,
|
|
16
|
+
connectionFilterSetofFunctions: true,
|
|
17
|
+
connectionFilterLogicalOperators: true,
|
|
18
|
+
connectionFilterAllowNullInput: false,
|
|
19
|
+
connectionFilterAllowEmptyObjectInput: false,
|
|
20
|
+
};
|
|
21
|
+
const PostGraphileConnectionFilterPlugin = (builder, configOptions = {}) => {
|
|
22
|
+
builder.hook('build', (build) => {
|
|
23
|
+
if (!build.versions) {
|
|
24
|
+
throw new Error(`Plugin ${pkg.name}@${pkg.version} requires graphile-build@^4.1.0 in order to check dependencies (current version: ${build.graphileBuildVersion})`);
|
|
25
|
+
}
|
|
26
|
+
const depends = (name, range) => {
|
|
27
|
+
if (!build.hasVersion(name, range)) {
|
|
28
|
+
throw new Error(`Plugin ${pkg.name}@${pkg.version} requires ${name}@${range} (${build.versions[name]
|
|
29
|
+
? `current version: ${build.versions[name]}`
|
|
30
|
+
: 'not found'})`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
depends('graphile-build-pg', '^4.5.0');
|
|
34
|
+
build.versions = build.extend(build.versions, { [pkg.name]: pkg.version });
|
|
35
|
+
return build;
|
|
36
|
+
});
|
|
37
|
+
const options = {
|
|
38
|
+
...defaultOptions,
|
|
39
|
+
...configOptions,
|
|
40
|
+
};
|
|
41
|
+
const { connectionFilterRelations, connectionFilterLogicalOperators } = options;
|
|
42
|
+
ConnectionArgFilterPlugin(builder, options);
|
|
43
|
+
PgConnectionArgFilterPlugin(builder, options);
|
|
44
|
+
PgConnectionArgFilterColumnsPlugin(builder, options);
|
|
45
|
+
PgConnectionArgFilterComputedColumnsPlugin(builder, options);
|
|
46
|
+
PgConnectionArgFilterCompositeTypeColumnsPlugin(builder, options);
|
|
47
|
+
PgConnectionArgFilterRecordFunctionsPlugin(builder, options);
|
|
48
|
+
if (connectionFilterRelations) {
|
|
49
|
+
PgConnectionArgFilterBackwardRelationsPlugin(builder, options);
|
|
50
|
+
PgConnectionArgFilterForwardRelationsPlugin(builder, options);
|
|
51
|
+
}
|
|
52
|
+
if (connectionFilterLogicalOperators) {
|
|
53
|
+
PgConnectionArgFilterLogicalOperatorsPlugin(builder, options);
|
|
54
|
+
}
|
|
55
|
+
PgConnectionArgFilterOperatorsPlugin(builder, options);
|
|
56
|
+
};
|
|
57
|
+
export { PostGraphileConnectionFilterPlugin };
|
|
58
|
+
export default PostGraphileConnectionFilterPlugin;
|
package/esm/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Plugin } from 'graphile-build';
|
|
2
|
+
import type { ConnectionFilterConfig, ConnectionFilterOptions } from './types';
|
|
3
|
+
declare const PostGraphileConnectionFilterPlugin: Plugin;
|
|
4
|
+
export type { ConnectionFilterConfig, ConnectionFilterOptions };
|
|
5
|
+
export { PostGraphileConnectionFilterPlugin };
|
|
6
|
+
export default PostGraphileConnectionFilterPlugin;
|
package/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
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.PostGraphileConnectionFilterPlugin = void 0;
|
|
7
|
+
const ConnectionArgFilterPlugin_1 = __importDefault(require("./ConnectionArgFilterPlugin"));
|
|
8
|
+
const PgConnectionArgFilterBackwardRelationsPlugin_1 = __importDefault(require("./PgConnectionArgFilterBackwardRelationsPlugin"));
|
|
9
|
+
const PgConnectionArgFilterColumnsPlugin_1 = __importDefault(require("./PgConnectionArgFilterColumnsPlugin"));
|
|
10
|
+
const PgConnectionArgFilterComputedColumnsPlugin_1 = __importDefault(require("./PgConnectionArgFilterComputedColumnsPlugin"));
|
|
11
|
+
const PgConnectionArgFilterCompositeTypeColumnsPlugin_1 = __importDefault(require("./PgConnectionArgFilterCompositeTypeColumnsPlugin"));
|
|
12
|
+
const PgConnectionArgFilterForwardRelationsPlugin_1 = __importDefault(require("./PgConnectionArgFilterForwardRelationsPlugin"));
|
|
13
|
+
const PgConnectionArgFilterLogicalOperatorsPlugin_1 = __importDefault(require("./PgConnectionArgFilterLogicalOperatorsPlugin"));
|
|
14
|
+
const PgConnectionArgFilterOperatorsPlugin_1 = __importDefault(require("./PgConnectionArgFilterOperatorsPlugin"));
|
|
15
|
+
const PgConnectionArgFilterPlugin_1 = __importDefault(require("./PgConnectionArgFilterPlugin"));
|
|
16
|
+
const PgConnectionArgFilterRecordFunctionsPlugin_1 = __importDefault(require("./PgConnectionArgFilterRecordFunctionsPlugin"));
|
|
17
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
18
|
+
const defaultOptions = {
|
|
19
|
+
connectionFilterArrays: true,
|
|
20
|
+
connectionFilterComputedColumns: true,
|
|
21
|
+
connectionFilterRelations: false,
|
|
22
|
+
connectionFilterSetofFunctions: true,
|
|
23
|
+
connectionFilterLogicalOperators: true,
|
|
24
|
+
connectionFilterAllowNullInput: false,
|
|
25
|
+
connectionFilterAllowEmptyObjectInput: false,
|
|
26
|
+
};
|
|
27
|
+
const PostGraphileConnectionFilterPlugin = (builder, configOptions = {}) => {
|
|
28
|
+
builder.hook('build', (build) => {
|
|
29
|
+
if (!build.versions) {
|
|
30
|
+
throw new Error(`Plugin ${package_json_1.default.name}@${package_json_1.default.version} requires graphile-build@^4.1.0 in order to check dependencies (current version: ${build.graphileBuildVersion})`);
|
|
31
|
+
}
|
|
32
|
+
const depends = (name, range) => {
|
|
33
|
+
if (!build.hasVersion(name, range)) {
|
|
34
|
+
throw new Error(`Plugin ${package_json_1.default.name}@${package_json_1.default.version} requires ${name}@${range} (${build.versions[name]
|
|
35
|
+
? `current version: ${build.versions[name]}`
|
|
36
|
+
: 'not found'})`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
depends('graphile-build-pg', '^4.5.0');
|
|
40
|
+
build.versions = build.extend(build.versions, { [package_json_1.default.name]: package_json_1.default.version });
|
|
41
|
+
return build;
|
|
42
|
+
});
|
|
43
|
+
const options = {
|
|
44
|
+
...defaultOptions,
|
|
45
|
+
...configOptions,
|
|
46
|
+
};
|
|
47
|
+
const { connectionFilterRelations, connectionFilterLogicalOperators } = options;
|
|
48
|
+
(0, ConnectionArgFilterPlugin_1.default)(builder, options);
|
|
49
|
+
(0, PgConnectionArgFilterPlugin_1.default)(builder, options);
|
|
50
|
+
(0, PgConnectionArgFilterColumnsPlugin_1.default)(builder, options);
|
|
51
|
+
(0, PgConnectionArgFilterComputedColumnsPlugin_1.default)(builder, options);
|
|
52
|
+
(0, PgConnectionArgFilterCompositeTypeColumnsPlugin_1.default)(builder, options);
|
|
53
|
+
(0, PgConnectionArgFilterRecordFunctionsPlugin_1.default)(builder, options);
|
|
54
|
+
if (connectionFilterRelations) {
|
|
55
|
+
(0, PgConnectionArgFilterBackwardRelationsPlugin_1.default)(builder, options);
|
|
56
|
+
(0, PgConnectionArgFilterForwardRelationsPlugin_1.default)(builder, options);
|
|
57
|
+
}
|
|
58
|
+
if (connectionFilterLogicalOperators) {
|
|
59
|
+
(0, PgConnectionArgFilterLogicalOperatorsPlugin_1.default)(builder, options);
|
|
60
|
+
}
|
|
61
|
+
(0, PgConnectionArgFilterOperatorsPlugin_1.default)(builder, options);
|
|
62
|
+
};
|
|
63
|
+
exports.PostGraphileConnectionFilterPlugin = PostGraphileConnectionFilterPlugin;
|
|
64
|
+
exports.default = PostGraphileConnectionFilterPlugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "graphile-plugin-connection-filter",
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "Filtering on PostGraphile connections",
|
|
5
|
+
"author": "Matt Bretl",
|
|
6
|
+
"homepage": "https://github.com/launchql/launchql",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"module": "esm/index.js",
|
|
10
|
+
"types": "index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"clean": "makage clean",
|
|
13
|
+
"copy": "makage assets",
|
|
14
|
+
"prepack": "pnpm run build",
|
|
15
|
+
"build": "makage build",
|
|
16
|
+
"build:dev": "makage build --dev",
|
|
17
|
+
"lint": "eslint . --fix",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"directory": "dist"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/launchql/launchql"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"postgraphile",
|
|
31
|
+
"graphile",
|
|
32
|
+
"plugin",
|
|
33
|
+
"postgres",
|
|
34
|
+
"graphql",
|
|
35
|
+
"filters",
|
|
36
|
+
"launchql"
|
|
37
|
+
],
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/launchql/launchql/issues"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"graphile-build": "^4.14.1",
|
|
43
|
+
"graphile-build-pg": "^4.14.1",
|
|
44
|
+
"graphql": "15.10.1"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"postgraphile": "^4.14.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@graphile-contrib/pg-simplify-inflector": "^6.1.0",
|
|
51
|
+
"@types/pg": "^8.15.6",
|
|
52
|
+
"graphile-test": "^2.8.9",
|
|
53
|
+
"makage": "^0.1.6",
|
|
54
|
+
"pg": "^8.16.0",
|
|
55
|
+
"pgsql-test": "^2.14.12"
|
|
56
|
+
},
|
|
57
|
+
"gitHead": "3812f24a480b2035b3413ec7fecfe492f294e590"
|
|
58
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SimpleCollectionSetting = 'omit' | 'both' | 'only';
|
|
2
|
+
export interface ConnectionFilterOptions {
|
|
3
|
+
connectionFilterAllowedOperators?: string[];
|
|
4
|
+
connectionFilterAllowedFieldTypes?: string[];
|
|
5
|
+
connectionFilterOperatorNames?: Record<string, string>;
|
|
6
|
+
connectionFilterUseListInflectors?: boolean;
|
|
7
|
+
connectionFilterArrays?: boolean;
|
|
8
|
+
connectionFilterComputedColumns?: boolean;
|
|
9
|
+
connectionFilterRelations?: boolean;
|
|
10
|
+
connectionFilterSetofFunctions?: boolean;
|
|
11
|
+
connectionFilterLogicalOperators?: boolean;
|
|
12
|
+
connectionFilterAllowNullInput?: boolean;
|
|
13
|
+
connectionFilterAllowEmptyObjectInput?: boolean;
|
|
14
|
+
pgSimpleCollections?: SimpleCollectionSetting;
|
|
15
|
+
pgOmitListSuffix?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type ConnectionFilterConfig = ConnectionFilterOptions & {
|
|
18
|
+
connectionFilterArrays: boolean;
|
|
19
|
+
connectionFilterComputedColumns: boolean;
|
|
20
|
+
connectionFilterRelations: boolean;
|
|
21
|
+
connectionFilterSetofFunctions: boolean;
|
|
22
|
+
connectionFilterLogicalOperators: boolean;
|
|
23
|
+
connectionFilterAllowNullInput: boolean;
|
|
24
|
+
connectionFilterAllowEmptyObjectInput: boolean;
|
|
25
|
+
};
|
package/types.js
ADDED