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.
Files changed (39) hide show
  1. package/ConnectionArgFilterPlugin.d.ts +3 -0
  2. package/ConnectionArgFilterPlugin.js +18 -0
  3. package/LICENSE +23 -0
  4. package/PgConnectionArgFilterBackwardRelationsPlugin.d.ts +12 -0
  5. package/PgConnectionArgFilterBackwardRelationsPlugin.js +244 -0
  6. package/PgConnectionArgFilterColumnsPlugin.d.ts +3 -0
  7. package/PgConnectionArgFilterColumnsPlugin.js +51 -0
  8. package/PgConnectionArgFilterCompositeTypeColumnsPlugin.d.ts +3 -0
  9. package/PgConnectionArgFilterCompositeTypeColumnsPlugin.js +67 -0
  10. package/PgConnectionArgFilterComputedColumnsPlugin.d.ts +3 -0
  11. package/PgConnectionArgFilterComputedColumnsPlugin.js +114 -0
  12. package/PgConnectionArgFilterForwardRelationsPlugin.d.ts +11 -0
  13. package/PgConnectionArgFilterForwardRelationsPlugin.js +130 -0
  14. package/PgConnectionArgFilterLogicalOperatorsPlugin.d.ts +3 -0
  15. package/PgConnectionArgFilterLogicalOperatorsPlugin.js +67 -0
  16. package/PgConnectionArgFilterOperatorsPlugin.d.ts +15 -0
  17. package/PgConnectionArgFilterOperatorsPlugin.js +551 -0
  18. package/PgConnectionArgFilterPlugin.d.ts +27 -0
  19. package/PgConnectionArgFilterPlugin.js +305 -0
  20. package/PgConnectionArgFilterRecordFunctionsPlugin.d.ts +3 -0
  21. package/PgConnectionArgFilterRecordFunctionsPlugin.js +75 -0
  22. package/README.md +364 -0
  23. package/esm/ConnectionArgFilterPlugin.js +16 -0
  24. package/esm/PgConnectionArgFilterBackwardRelationsPlugin.js +242 -0
  25. package/esm/PgConnectionArgFilterColumnsPlugin.js +49 -0
  26. package/esm/PgConnectionArgFilterCompositeTypeColumnsPlugin.js +65 -0
  27. package/esm/PgConnectionArgFilterComputedColumnsPlugin.js +112 -0
  28. package/esm/PgConnectionArgFilterForwardRelationsPlugin.js +128 -0
  29. package/esm/PgConnectionArgFilterLogicalOperatorsPlugin.js +65 -0
  30. package/esm/PgConnectionArgFilterOperatorsPlugin.js +549 -0
  31. package/esm/PgConnectionArgFilterPlugin.js +303 -0
  32. package/esm/PgConnectionArgFilterRecordFunctionsPlugin.js +73 -0
  33. package/esm/index.js +58 -0
  34. package/esm/types.js +1 -0
  35. package/index.d.ts +6 -0
  36. package/index.js +64 -0
  37. package/package.json +58 -0
  38. package/types.d.ts +25 -0
  39. package/types.js +2 -0
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'graphile-build';
2
+ declare const ConnectionArgFilterPlugin: Plugin;
3
+ export default ConnectionArgFilterPlugin;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ConnectionArgFilterPlugin = (builder) => {
4
+ builder.hook('inflection', (inflection) => {
5
+ return Object.assign(inflection, {
6
+ filterType(typeName) {
7
+ return `${typeName}Filter`;
8
+ },
9
+ filterFieldType(typeName) {
10
+ return `${typeName}Filter`;
11
+ },
12
+ filterFieldListType(typeName) {
13
+ return `${typeName}ListFilter`;
14
+ },
15
+ });
16
+ });
17
+ };
18
+ exports.default = ConnectionArgFilterPlugin;
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
4
+ Copyright (c) 2025 Hyperweb <developers@hyperweb.io>
5
+ Copyright (c) 2020-present, Interweb, Inc.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,12 @@
1
+ import type { Plugin } from 'graphile-build';
2
+ import type { PgAttribute, PgClass, PgConstraint } from 'graphile-build-pg';
3
+ declare const PgConnectionArgFilterBackwardRelationsPlugin: Plugin;
4
+ export interface BackwardRelationSpec {
5
+ table: PgClass;
6
+ keyAttributes: PgAttribute[];
7
+ foreignTable: PgClass;
8
+ foreignKeyAttributes: PgAttribute[];
9
+ foreignConstraint: PgConstraint;
10
+ isOneToMany: boolean;
11
+ }
12
+ export default PgConnectionArgFilterBackwardRelationsPlugin;
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PgConnectionArgFilterBackwardRelationsPlugin = (builder, rawOptions) => {
4
+ const { pgSimpleCollections, pgOmitListSuffix, connectionFilterUseListInflectors, } = rawOptions;
5
+ const hasConnections = pgSimpleCollections !== 'only';
6
+ const simpleInflectorsAreShorter = pgOmitListSuffix === true;
7
+ if (simpleInflectorsAreShorter &&
8
+ connectionFilterUseListInflectors === undefined) {
9
+ // TODO: in V3 consider doing this for the user automatically (doing it in V2 would be a breaking change)
10
+ console.warn(`We recommend you set the 'connectionFilterUseListInflectors' option to 'true' since you've set the 'pgOmitListSuffix' option`);
11
+ }
12
+ const useConnectionInflectors = connectionFilterUseListInflectors === undefined
13
+ ? hasConnections
14
+ : !connectionFilterUseListInflectors;
15
+ builder.hook('inflection', (inflection) => {
16
+ return Object.assign(inflection, {
17
+ filterManyType(table, foreignTable) {
18
+ return this.upperCamelCase(`${this.tableType(table)}-to-many-${this.tableType(foreignTable)}-filter`);
19
+ },
20
+ filterBackwardSingleRelationExistsFieldName(relationFieldName) {
21
+ return `${relationFieldName}Exists`;
22
+ },
23
+ filterBackwardManyRelationExistsFieldName(relationFieldName) {
24
+ return `${relationFieldName}Exist`;
25
+ },
26
+ filterSingleRelationByKeysBackwardsFieldName(fieldName) {
27
+ return fieldName;
28
+ },
29
+ filterManyRelationByKeysFieldName(fieldName) {
30
+ return fieldName;
31
+ },
32
+ });
33
+ });
34
+ builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
35
+ const { describePgEntity, extend, newWithHooks, inflection, pgOmit: omit, pgSql: sql, pgIntrospectionResultsByKind: introspectionResultsByKind, graphql: { GraphQLInputObjectType, GraphQLBoolean }, connectionFilterResolve, connectionFilterRegisterResolver, connectionFilterTypesByTypeName, connectionFilterType, } = build;
36
+ const { fieldWithHooks, scope: { pgIntrospection: table, isPgConnectionFilter }, Self, } = context;
37
+ if (!isPgConnectionFilter || table.kind !== 'class')
38
+ return fields;
39
+ connectionFilterTypesByTypeName[Self.name] = Self;
40
+ const backwardRelationSpecs = introspectionResultsByKind.constraint
41
+ .filter((con) => con.type === 'f')
42
+ .filter((con) => con.foreignClassId === table.id)
43
+ .reduce((memo, foreignConstraint) => {
44
+ if (omit(foreignConstraint, 'read') ||
45
+ omit(foreignConstraint, 'filter')) {
46
+ return memo;
47
+ }
48
+ const foreignTable = introspectionResultsByKind.classById[foreignConstraint.classId];
49
+ if (!foreignTable) {
50
+ throw new Error(`Could not find the foreign table (constraint: ${foreignConstraint.name})`);
51
+ }
52
+ if (omit(foreignTable, 'read') || omit(foreignTable, 'filter')) {
53
+ return memo;
54
+ }
55
+ const attributes = introspectionResultsByKind.attribute
56
+ .filter((attr) => attr.classId === table.id)
57
+ .sort((a, b) => a.num - b.num);
58
+ const foreignAttributes = introspectionResultsByKind.attribute
59
+ .filter((attr) => attr.classId === foreignTable.id)
60
+ .sort((a, b) => a.num - b.num);
61
+ const keyAttributes = foreignConstraint.foreignKeyAttributeNums.map((num) => attributes.filter((attr) => attr.num === num)[0]);
62
+ const foreignKeyAttributes = foreignConstraint.keyAttributeNums.map((num) => foreignAttributes.filter((attr) => attr.num === num)[0]);
63
+ if (keyAttributes.some((attr) => omit(attr, 'read'))) {
64
+ return memo;
65
+ }
66
+ if (foreignKeyAttributes.some((attr) => omit(attr, 'read'))) {
67
+ return memo;
68
+ }
69
+ const isForeignKeyUnique = !!introspectionResultsByKind.constraint.find((c) => c.classId === foreignTable.id &&
70
+ (c.type === 'p' || c.type === 'u') &&
71
+ c.keyAttributeNums.length === foreignKeyAttributes.length &&
72
+ c.keyAttributeNums.every((n, i) => foreignKeyAttributes[i].num === n));
73
+ memo.push({
74
+ table,
75
+ keyAttributes,
76
+ foreignTable,
77
+ foreignKeyAttributes,
78
+ foreignConstraint,
79
+ isOneToMany: !isForeignKeyUnique,
80
+ });
81
+ return memo;
82
+ }, []);
83
+ let backwardRelationSpecByFieldName = {};
84
+ const addField = (fieldName, description, type, resolve, spec, hint) => {
85
+ // Field
86
+ fields = extend(fields, {
87
+ [fieldName]: fieldWithHooks(fieldName, {
88
+ description,
89
+ type,
90
+ }, {
91
+ isPgConnectionFilterField: true,
92
+ }),
93
+ }, hint);
94
+ // Relation spec for use in resolver
95
+ backwardRelationSpecByFieldName = extend(backwardRelationSpecByFieldName, {
96
+ [fieldName]: spec,
97
+ });
98
+ // Resolver
99
+ connectionFilterRegisterResolver(Self.name, fieldName, resolve);
100
+ };
101
+ const resolveSingle = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
102
+ if (fieldValue == null)
103
+ return null;
104
+ const { foreignTable, foreignKeyAttributes, keyAttributes } = backwardRelationSpecByFieldName[fieldName];
105
+ const foreignTableTypeName = inflection.tableType(foreignTable);
106
+ const foreignTableAlias = sql.identifier(Symbol());
107
+ const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
108
+ const sqlIdentifier = sql.identifier(foreignTable.namespace.name, foreignTable.name);
109
+ const sqlKeysMatch = sql.query `(${sql.join(foreignKeyAttributes.map((attr, i) => {
110
+ return sql.fragment `${foreignTableAlias}.${sql.identifier(attr.name)} = ${sourceAlias}.${sql.identifier(keyAttributes[i].name)}`;
111
+ }), ') and (')})`;
112
+ const sqlSelectWhereKeysMatch = sql.query `select 1 from ${sqlIdentifier} as ${foreignTableAlias} where ${sqlKeysMatch}`;
113
+ const sqlFragment = connectionFilterResolve(fieldValue, foreignTableAlias, foreignTableFilterTypeName, queryBuilder);
114
+ return sqlFragment == null
115
+ ? null
116
+ : sql.query `exists(${sqlSelectWhereKeysMatch} and (${sqlFragment}))`;
117
+ };
118
+ const resolveExists = ({ sourceAlias, fieldName, fieldValue, }) => {
119
+ if (fieldValue == null)
120
+ return null;
121
+ const { foreignTable, foreignKeyAttributes, keyAttributes } = backwardRelationSpecByFieldName[fieldName];
122
+ const foreignTableAlias = sql.identifier(Symbol());
123
+ const sqlIdentifier = sql.identifier(foreignTable.namespace.name, foreignTable.name);
124
+ const sqlKeysMatch = sql.query `(${sql.join(foreignKeyAttributes.map((attr, i) => {
125
+ return sql.fragment `${foreignTableAlias}.${sql.identifier(attr.name)} = ${sourceAlias}.${sql.identifier(keyAttributes[i].name)}`;
126
+ }), ') and (')})`;
127
+ const sqlSelectWhereKeysMatch = sql.query `select 1 from ${sqlIdentifier} as ${foreignTableAlias} where ${sqlKeysMatch}`;
128
+ return fieldValue === true
129
+ ? sql.query `exists(${sqlSelectWhereKeysMatch})`
130
+ : sql.query `not exists(${sqlSelectWhereKeysMatch})`;
131
+ };
132
+ const makeResolveMany = (backwardRelationSpec) => {
133
+ const resolveMany = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
134
+ if (fieldValue == null)
135
+ return null;
136
+ const { foreignTable } = backwardRelationSpecByFieldName[fieldName];
137
+ const foreignTableFilterManyTypeName = inflection.filterManyType(table, foreignTable);
138
+ const sqlFragment = connectionFilterResolve(fieldValue, sourceAlias, foreignTableFilterManyTypeName, queryBuilder, null, null, null, { backwardRelationSpec });
139
+ return sqlFragment == null ? null : sqlFragment;
140
+ };
141
+ return resolveMany;
142
+ };
143
+ for (const spec of backwardRelationSpecs) {
144
+ const { foreignTable, foreignKeyAttributes, foreignConstraint, isOneToMany, } = spec;
145
+ const foreignTableTypeName = inflection.tableType(foreignTable);
146
+ const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
147
+ const ForeignTableFilterType = connectionFilterType(newWithHooks, foreignTableFilterTypeName, foreignTable, foreignTableTypeName);
148
+ if (!ForeignTableFilterType)
149
+ continue;
150
+ if (isOneToMany) {
151
+ if (!omit(foreignTable, 'many')) {
152
+ const filterManyTypeName = inflection.filterManyType(table, foreignTable);
153
+ if (!connectionFilterTypesByTypeName[filterManyTypeName]) {
154
+ connectionFilterTypesByTypeName[filterManyTypeName] = newWithHooks(GraphQLInputObjectType, {
155
+ name: filterManyTypeName,
156
+ description: `A filter to be used against many \`${foreignTableTypeName}\` object types. All fields are combined with a logical ‘and.’`,
157
+ }, {
158
+ foreignTable,
159
+ isPgConnectionFilterMany: true,
160
+ });
161
+ }
162
+ const FilterManyType = connectionFilterTypesByTypeName[filterManyTypeName];
163
+ const fieldName = useConnectionInflectors
164
+ ? inflection.manyRelationByKeys(foreignKeyAttributes, foreignTable, table, foreignConstraint)
165
+ : inflection.manyRelationByKeysSimple(foreignKeyAttributes, foreignTable, table, foreignConstraint);
166
+ const filterFieldName = inflection.filterManyRelationByKeysFieldName(fieldName);
167
+ addField(filterFieldName, `Filter by the object’s \`${fieldName}\` relation.`, FilterManyType, makeResolveMany(spec), spec, `Adding connection filter backward relation field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`);
168
+ const existsFieldName = inflection.filterBackwardManyRelationExistsFieldName(fieldName);
169
+ addField(existsFieldName, `Some related \`${fieldName}\` exist.`, GraphQLBoolean, resolveExists, spec, `Adding connection filter backward relation exists field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`);
170
+ }
171
+ }
172
+ else {
173
+ const fieldName = inflection.singleRelationByKeysBackwards(foreignKeyAttributes, foreignTable, table, foreignConstraint);
174
+ const filterFieldName = inflection.filterSingleRelationByKeysBackwardsFieldName(fieldName);
175
+ addField(filterFieldName, `Filter by the object’s \`${fieldName}\` relation.`, ForeignTableFilterType, resolveSingle, spec, `Adding connection filter backward relation field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`);
176
+ const existsFieldName = inflection.filterBackwardSingleRelationExistsFieldName(fieldName);
177
+ addField(existsFieldName, `A related \`${fieldName}\` exists.`, GraphQLBoolean, resolveExists, spec, `Adding connection filter backward relation exists field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`);
178
+ }
179
+ }
180
+ return fields;
181
+ });
182
+ builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
183
+ const { extend, newWithHooks, inflection, pgSql: sql, connectionFilterResolve, connectionFilterRegisterResolver, connectionFilterTypesByTypeName, connectionFilterType, } = build;
184
+ const { fieldWithHooks, scope: { foreignTable, isPgConnectionFilterMany }, Self, } = context;
185
+ if (!isPgConnectionFilterMany || !foreignTable)
186
+ return fields;
187
+ connectionFilterTypesByTypeName[Self.name] = Self;
188
+ const foreignTableTypeName = inflection.tableType(foreignTable);
189
+ const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
190
+ const FilterType = connectionFilterType(newWithHooks, foreignTableFilterTypeName, foreignTable, foreignTableTypeName);
191
+ const manyFields = {
192
+ every: fieldWithHooks('every', {
193
+ description: `Every related \`${foreignTableTypeName}\` matches the filter criteria. All fields are combined with a logical ‘and.’`,
194
+ type: FilterType,
195
+ }, {
196
+ isPgConnectionFilterManyField: true,
197
+ }),
198
+ some: fieldWithHooks('some', {
199
+ description: `Some related \`${foreignTableTypeName}\` matches the filter criteria. All fields are combined with a logical ‘and.’`,
200
+ type: FilterType,
201
+ }, {
202
+ isPgConnectionFilterManyField: true,
203
+ }),
204
+ none: fieldWithHooks('none', {
205
+ description: `No related \`${foreignTableTypeName}\` matches the filter criteria. All fields are combined with a logical ‘and.’`,
206
+ type: FilterType,
207
+ }, {
208
+ isPgConnectionFilterManyField: true,
209
+ }),
210
+ };
211
+ const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, parentFieldInfo, }) => {
212
+ if (fieldValue == null)
213
+ return null;
214
+ if (!parentFieldInfo || !parentFieldInfo.backwardRelationSpec)
215
+ throw new Error('Did not receive backward relation spec');
216
+ const { keyAttributes, foreignKeyAttributes } = parentFieldInfo.backwardRelationSpec;
217
+ const foreignTableAlias = sql.identifier(Symbol());
218
+ const sqlIdentifier = sql.identifier(foreignTable.namespace.name, foreignTable.name);
219
+ const sqlKeysMatch = sql.query `(${sql.join(foreignKeyAttributes.map((attr, i) => {
220
+ return sql.fragment `${foreignTableAlias}.${sql.identifier(attr.name)} = ${sourceAlias}.${sql.identifier(keyAttributes[i].name)}`;
221
+ }), ') and (')})`;
222
+ const sqlSelectWhereKeysMatch = sql.query `select 1 from ${sqlIdentifier} as ${foreignTableAlias} where ${sqlKeysMatch}`;
223
+ const sqlFragment = connectionFilterResolve(fieldValue, foreignTableAlias, foreignTableFilterTypeName, queryBuilder);
224
+ if (sqlFragment == null) {
225
+ return null;
226
+ }
227
+ else if (fieldName === 'every') {
228
+ return sql.query `not exists(${sqlSelectWhereKeysMatch} and not (${sqlFragment}))`;
229
+ }
230
+ else if (fieldName === 'some') {
231
+ return sql.query `exists(${sqlSelectWhereKeysMatch} and (${sqlFragment}))`;
232
+ }
233
+ else if (fieldName === 'none') {
234
+ return sql.query `not exists(${sqlSelectWhereKeysMatch} and (${sqlFragment}))`;
235
+ }
236
+ throw new Error(`Unknown field name: ${fieldName}`);
237
+ };
238
+ for (const fieldName of Object.keys(manyFields)) {
239
+ connectionFilterRegisterResolver(Self.name, fieldName, resolve);
240
+ }
241
+ return extend(fields, manyFields);
242
+ });
243
+ };
244
+ exports.default = PgConnectionArgFilterBackwardRelationsPlugin;
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'graphile-build';
2
+ declare const PgConnectionArgFilterColumnsPlugin: Plugin;
3
+ export default PgConnectionArgFilterColumnsPlugin;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PgConnectionArgFilterColumnsPlugin = (builder) => {
4
+ builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
5
+ const { extend, newWithHooks, pgSql: sql, pgIntrospectionResultsByKind: introspectionResultsByKind, pgColumnFilter, pgOmit: omit, inflection, connectionFilterOperatorsType, connectionFilterRegisterResolver, connectionFilterResolve, connectionFilterTypesByTypeName, } = build;
6
+ const { fieldWithHooks, scope: { pgIntrospection: table, isPgConnectionFilter }, Self, } = context;
7
+ if (!isPgConnectionFilter || table.kind !== 'class')
8
+ return fields;
9
+ connectionFilterTypesByTypeName[Self.name] = Self;
10
+ const attrByFieldName = introspectionResultsByKind.attribute
11
+ .filter((attr) => attr.classId === table.id)
12
+ .filter((attr) => pgColumnFilter(attr, build, context))
13
+ .filter((attr) => !omit(attr, 'filter'))
14
+ .reduce((memo, attr) => {
15
+ const fieldName = inflection.column(attr);
16
+ memo[fieldName] = attr;
17
+ return memo;
18
+ }, {});
19
+ const operatorsTypeNameByFieldName = {};
20
+ const attrFields = Object.entries(attrByFieldName).reduce((memo, [fieldName, attr]) => {
21
+ const OperatorsType = connectionFilterOperatorsType(newWithHooks, attr.typeId, attr.typeModifier);
22
+ if (!OperatorsType) {
23
+ return memo;
24
+ }
25
+ operatorsTypeNameByFieldName[fieldName] = OperatorsType.name;
26
+ return extend(memo, {
27
+ [fieldName]: fieldWithHooks(fieldName, {
28
+ description: `Filter by the object’s \`${fieldName}\` field.`,
29
+ type: OperatorsType,
30
+ }, {
31
+ isPgConnectionFilterField: true,
32
+ }),
33
+ });
34
+ }, {});
35
+ const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
36
+ if (fieldValue == null)
37
+ return null;
38
+ const attr = attrByFieldName[fieldName];
39
+ const sqlIdentifier = sql.query `${sourceAlias}.${sql.identifier(attr.name)}`;
40
+ const pgType = attr.type;
41
+ const pgTypeModifier = attr.typeModifier;
42
+ const filterTypeName = operatorsTypeNameByFieldName[fieldName];
43
+ return connectionFilterResolve(fieldValue, sqlIdentifier, filterTypeName, queryBuilder, pgType, pgTypeModifier, fieldName);
44
+ };
45
+ for (const fieldName of Object.keys(attrFields)) {
46
+ connectionFilterRegisterResolver(Self.name, fieldName, resolve);
47
+ }
48
+ return extend(fields, attrFields);
49
+ });
50
+ };
51
+ exports.default = PgConnectionArgFilterColumnsPlugin;
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'graphile-build';
2
+ declare const PgConnectionArgFilterCompositeTypeColumnsPlugin: Plugin;
3
+ export default PgConnectionArgFilterCompositeTypeColumnsPlugin;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PgConnectionArgFilterCompositeTypeColumnsPlugin = (builder, rawOptions) => {
4
+ const { connectionFilterAllowedFieldTypes } = rawOptions;
5
+ builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
6
+ const { extend, newWithHooks, pgSql: sql, pgIntrospectionResultsByKind: introspectionResultsByKind, pgGetGqlTypeByTypeIdAndModifier, pgColumnFilter, pgOmit: omit, inflection, connectionFilterRegisterResolver, connectionFilterResolve, connectionFilterType, connectionFilterTypesByTypeName, } = build;
7
+ const { fieldWithHooks, scope: { pgIntrospection: table, isPgConnectionFilter }, Self, } = context;
8
+ if (!isPgConnectionFilter || table.kind !== 'class')
9
+ return fields;
10
+ connectionFilterTypesByTypeName[Self.name] = Self;
11
+ const attrByFieldName = introspectionResultsByKind.attribute
12
+ .filter((attr) => attr.classId === table.id)
13
+ .filter((attr) => pgColumnFilter(attr, build, context))
14
+ .filter((attr) => !omit(attr, 'filter'))
15
+ .filter((attr) => attr.type &&
16
+ attr.type.type === 'c' &&
17
+ attr.type.class &&
18
+ !attr.type.class.isSelectable) // keep only the composite type columns
19
+ .reduce((memo, attr) => {
20
+ const fieldName = inflection.column(attr);
21
+ memo[fieldName] = attr;
22
+ return memo;
23
+ }, {});
24
+ const filterTypeNameByFieldName = {};
25
+ const attrFields = Object.entries(attrByFieldName).reduce((memo, [fieldName, attr]) => {
26
+ const NodeType = pgGetGqlTypeByTypeIdAndModifier(attr.typeId, attr.typeModifier);
27
+ if (!NodeType) {
28
+ return memo;
29
+ }
30
+ const nodeTypeName = NodeType.name;
31
+ // Respect `connectionFilterAllowedFieldTypes` config option
32
+ if (connectionFilterAllowedFieldTypes &&
33
+ !connectionFilterAllowedFieldTypes.includes(nodeTypeName)) {
34
+ return memo;
35
+ }
36
+ const filterTypeName = inflection.filterType(nodeTypeName);
37
+ const CompositeFilterType = connectionFilterType(newWithHooks, filterTypeName, attr.type.class, nodeTypeName);
38
+ if (!CompositeFilterType) {
39
+ return memo;
40
+ }
41
+ filterTypeNameByFieldName[fieldName] = filterTypeName;
42
+ return extend(memo, {
43
+ [fieldName]: fieldWithHooks(fieldName, {
44
+ description: `Filter by the object’s \`${fieldName}\` field.`,
45
+ type: CompositeFilterType,
46
+ }, {
47
+ isPgConnectionFilterField: true,
48
+ }),
49
+ });
50
+ }, {});
51
+ const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
52
+ if (fieldValue == null)
53
+ return null;
54
+ const attr = attrByFieldName[fieldName];
55
+ const sqlIdentifier = sql.query `(${sourceAlias}.${sql.identifier(attr.name)})`; // parentheses are required to avoid confusing the parser
56
+ const pgType = attr.type;
57
+ const pgTypeModifier = attr.typeModifier;
58
+ const filterTypeName = filterTypeNameByFieldName[fieldName];
59
+ return connectionFilterResolve(fieldValue, sqlIdentifier, filterTypeName, queryBuilder, pgType, pgTypeModifier, fieldName);
60
+ };
61
+ for (const fieldName of Object.keys(attrFields)) {
62
+ connectionFilterRegisterResolver(Self.name, fieldName, resolve);
63
+ }
64
+ return extend(fields, attrFields);
65
+ });
66
+ };
67
+ exports.default = PgConnectionArgFilterCompositeTypeColumnsPlugin;
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'graphile-build';
2
+ declare const PgConnectionArgFilterComputedColumnsPlugin: Plugin;
3
+ export default PgConnectionArgFilterComputedColumnsPlugin;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PgConnectionArgFilterComputedColumnsPlugin = (builder, rawOptions) => {
4
+ const { connectionFilterComputedColumns } = rawOptions;
5
+ builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
6
+ const { extend, newWithHooks, pgIntrospectionResultsByKind: introspectionResultsByKind, pgOmit: omit, pgSql: sql, inflection, connectionFilterOperatorsType, connectionFilterRegisterResolver, connectionFilterResolve, connectionFilterTypesByTypeName, } = build;
7
+ const { scope: { isPgConnectionFilter, pgIntrospection: table }, fieldWithHooks, Self, } = context;
8
+ if (!isPgConnectionFilter || !table || table.kind !== 'class') {
9
+ return fields;
10
+ }
11
+ connectionFilterTypesByTypeName[Self.name] = Self;
12
+ const procByFieldName = introspectionResultsByKind.procedure.reduce((memo, proc) => {
13
+ // Must be marked @filterable OR enabled via plugin option
14
+ if (!(proc.tags.filterable || connectionFilterComputedColumns))
15
+ return memo;
16
+ // Must not be omitted
17
+ if (omit(proc, 'execute'))
18
+ return memo;
19
+ if (omit(proc, 'filter'))
20
+ return memo;
21
+ // Must be a computed column
22
+ const computedColumnDetails = getComputedColumnDetails(build, table, proc);
23
+ if (!computedColumnDetails)
24
+ return memo;
25
+ const { pseudoColumnName } = computedColumnDetails;
26
+ // Must have only one required argument
27
+ const inputArgsCount = proc.argTypeIds.filter((_typeId, idx) => proc.argModes.length === 0 || // all args are `in`
28
+ proc.argModes[idx] === 'i' || // this arg is `in`
29
+ proc.argModes[idx] === 'b' // this arg is `inout`
30
+ ).length;
31
+ const nonOptionalArgumentsCount = inputArgsCount - proc.argDefaultsNum;
32
+ if (nonOptionalArgumentsCount > 1) {
33
+ return memo;
34
+ }
35
+ // Must return a scalar or an array
36
+ if (proc.returnsSet)
37
+ return memo;
38
+ const returnType = introspectionResultsByKind.typeById[proc.returnTypeId];
39
+ const returnTypeTable = introspectionResultsByKind.classById[returnType.classId];
40
+ if (returnTypeTable)
41
+ return memo;
42
+ const isRecordLike = returnType.id === '2249';
43
+ if (isRecordLike)
44
+ return memo;
45
+ const isVoid = String(returnType.id) === '2278';
46
+ if (isVoid)
47
+ return memo;
48
+ // Looks good
49
+ const fieldName = inflection.computedColumn(pseudoColumnName, proc, table);
50
+ memo = build.extend(memo, { [fieldName]: proc });
51
+ return memo;
52
+ }, {});
53
+ const operatorsTypeNameByFieldName = {};
54
+ const procFields = Object.entries(procByFieldName).reduce((memo, [fieldName, proc]) => {
55
+ const OperatorsType = connectionFilterOperatorsType(newWithHooks, proc.returnTypeId, null);
56
+ if (!OperatorsType) {
57
+ return memo;
58
+ }
59
+ operatorsTypeNameByFieldName[fieldName] = OperatorsType.name;
60
+ return extend(memo, {
61
+ [fieldName]: fieldWithHooks(fieldName, {
62
+ description: `Filter by the object’s \`${fieldName}\` field.`,
63
+ type: OperatorsType,
64
+ }, {
65
+ isPgConnectionFilterField: true,
66
+ }),
67
+ });
68
+ }, {});
69
+ const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
70
+ if (fieldValue == null)
71
+ return null;
72
+ const proc = procByFieldName[fieldName];
73
+ const sqlIdentifier = sql.query `${sql.identifier(proc.namespace.name)}.${sql.identifier(proc.name)}(${sourceAlias})`;
74
+ const pgType = introspectionResultsByKind.typeById[proc.returnTypeId];
75
+ const pgTypeModifier = null;
76
+ const filterTypeName = operatorsTypeNameByFieldName[fieldName];
77
+ return connectionFilterResolve(fieldValue, sqlIdentifier, filterTypeName, queryBuilder, pgType, pgTypeModifier, fieldName);
78
+ };
79
+ for (const fieldName of Object.keys(procFields)) {
80
+ connectionFilterRegisterResolver(Self.name, fieldName, resolve);
81
+ }
82
+ return extend(fields, procFields);
83
+ });
84
+ function getComputedColumnDetails(build, table, proc) {
85
+ if (!proc.isStable)
86
+ return null;
87
+ if (proc.namespaceId !== table.namespaceId)
88
+ return null;
89
+ if (!proc.name.startsWith(`${table.name}_`))
90
+ return null;
91
+ if (proc.argTypeIds.length < 1)
92
+ return null;
93
+ if (proc.argTypeIds[0] !== table.type.id)
94
+ return null;
95
+ const argTypes = proc.argTypeIds.reduce((prev, typeId, idx) => {
96
+ if (proc.argModes.length === 0 || // all args are `in`
97
+ proc.argModes[idx] === 'i' || // this arg is `in`
98
+ proc.argModes[idx] === 'b' // this arg is `inout`
99
+ ) {
100
+ prev.push(build.pgIntrospectionResultsByKind.typeById[typeId]);
101
+ }
102
+ return prev;
103
+ }, []);
104
+ if (argTypes
105
+ .slice(1)
106
+ .some((type) => type.type === 'c' && type.class && type.class.isSelectable)) {
107
+ // Accepts two input tables? Skip.
108
+ return null;
109
+ }
110
+ const pseudoColumnName = proc.name.substr(table.name.length + 1);
111
+ return { argTypes, pseudoColumnName };
112
+ }
113
+ };
114
+ exports.default = PgConnectionArgFilterComputedColumnsPlugin;
@@ -0,0 +1,11 @@
1
+ import type { Plugin } from 'graphile-build';
2
+ import type { PgAttribute, PgClass, PgConstraint } from 'graphile-build-pg';
3
+ declare const PgConnectionArgFilterForwardRelationsPlugin: Plugin;
4
+ export interface ForwardRelationSpec {
5
+ table: PgClass;
6
+ keyAttributes: PgAttribute[];
7
+ foreignTable: PgClass;
8
+ foreignKeyAttributes: PgAttribute[];
9
+ constraint: PgConstraint;
10
+ }
11
+ export default PgConnectionArgFilterForwardRelationsPlugin;