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,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const PgConnectionArgFilterForwardRelationsPlugin = (builder) => {
|
|
4
|
+
builder.hook('inflection', (inflection) => ({
|
|
5
|
+
...inflection,
|
|
6
|
+
filterForwardRelationExistsFieldName(relationFieldName) {
|
|
7
|
+
return `${relationFieldName}Exists`;
|
|
8
|
+
},
|
|
9
|
+
filterSingleRelationFieldName(fieldName) {
|
|
10
|
+
return fieldName;
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
|
|
14
|
+
const { describePgEntity, extend, newWithHooks, inflection, graphql: { GraphQLBoolean }, pgOmit: omit, pgSql: sql, pgIntrospectionResultsByKind: introspectionResultsByKind, connectionFilterResolve, connectionFilterRegisterResolver, connectionFilterTypesByTypeName, connectionFilterType, } = build;
|
|
15
|
+
const { fieldWithHooks, scope: { pgIntrospection: table, isPgConnectionFilter }, Self, } = context;
|
|
16
|
+
if (!isPgConnectionFilter || table.kind !== 'class')
|
|
17
|
+
return fields;
|
|
18
|
+
connectionFilterTypesByTypeName[Self.name] = Self;
|
|
19
|
+
const forwardRelationSpecs = introspectionResultsByKind.constraint
|
|
20
|
+
.filter((con) => con.type === 'f')
|
|
21
|
+
.filter((con) => con.classId === table.id)
|
|
22
|
+
.reduce((memo, constraint) => {
|
|
23
|
+
if (omit(constraint, 'read') || omit(constraint, 'filter')) {
|
|
24
|
+
return memo;
|
|
25
|
+
}
|
|
26
|
+
const foreignTable = constraint.foreignClassId
|
|
27
|
+
? introspectionResultsByKind.classById[constraint.foreignClassId]
|
|
28
|
+
: null;
|
|
29
|
+
if (!foreignTable) {
|
|
30
|
+
throw new Error(`Could not find the foreign table (constraint: ${constraint.name})`);
|
|
31
|
+
}
|
|
32
|
+
if (omit(foreignTable, 'read') || omit(foreignTable, 'filter')) {
|
|
33
|
+
return memo;
|
|
34
|
+
}
|
|
35
|
+
const attributes = introspectionResultsByKind.attribute
|
|
36
|
+
.filter((attr) => attr.classId === table.id)
|
|
37
|
+
.sort((a, b) => a.num - b.num);
|
|
38
|
+
const foreignAttributes = introspectionResultsByKind.attribute
|
|
39
|
+
.filter((attr) => attr.classId === foreignTable.id)
|
|
40
|
+
.sort((a, b) => a.num - b.num);
|
|
41
|
+
const keyAttributes = constraint.keyAttributeNums.map((num) => attributes.filter((attr) => attr.num === num)[0]);
|
|
42
|
+
const foreignKeyAttributes = constraint.foreignKeyAttributeNums.map((num) => foreignAttributes.filter((attr) => attr.num === num)[0]);
|
|
43
|
+
if (keyAttributes.some((attr) => omit(attr, 'read'))) {
|
|
44
|
+
return memo;
|
|
45
|
+
}
|
|
46
|
+
if (foreignKeyAttributes.some((attr) => omit(attr, 'read'))) {
|
|
47
|
+
return memo;
|
|
48
|
+
}
|
|
49
|
+
memo.push({
|
|
50
|
+
table,
|
|
51
|
+
keyAttributes,
|
|
52
|
+
foreignTable,
|
|
53
|
+
foreignKeyAttributes,
|
|
54
|
+
constraint,
|
|
55
|
+
});
|
|
56
|
+
return memo;
|
|
57
|
+
}, []);
|
|
58
|
+
let forwardRelationSpecByFieldName = {};
|
|
59
|
+
const addField = (fieldName, description, type, resolve, spec, hint) => {
|
|
60
|
+
// Field
|
|
61
|
+
fields = extend(fields, {
|
|
62
|
+
[fieldName]: fieldWithHooks(fieldName, {
|
|
63
|
+
description,
|
|
64
|
+
type,
|
|
65
|
+
}, {
|
|
66
|
+
isPgConnectionFilterField: true,
|
|
67
|
+
}),
|
|
68
|
+
}, hint);
|
|
69
|
+
// Spec for use in resolver
|
|
70
|
+
forwardRelationSpecByFieldName = extend(forwardRelationSpecByFieldName, {
|
|
71
|
+
[fieldName]: spec,
|
|
72
|
+
});
|
|
73
|
+
// Resolver
|
|
74
|
+
connectionFilterRegisterResolver(Self.name, fieldName, resolve);
|
|
75
|
+
};
|
|
76
|
+
const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
|
|
77
|
+
if (fieldValue == null)
|
|
78
|
+
return null;
|
|
79
|
+
const { foreignTable, foreignKeyAttributes, keyAttributes } = forwardRelationSpecByFieldName[fieldName];
|
|
80
|
+
const foreignTableAlias = sql.identifier(Symbol());
|
|
81
|
+
const sqlIdentifier = sql.identifier(foreignTable.namespace.name, foreignTable.name);
|
|
82
|
+
const sqlKeysMatch = sql.query `(${sql.join(keyAttributes.map((key, i) => {
|
|
83
|
+
return sql.fragment `${sourceAlias}.${sql.identifier(key.name)} = ${foreignTableAlias}.${sql.identifier(foreignKeyAttributes[i].name)}`;
|
|
84
|
+
}), ') and (')})`;
|
|
85
|
+
const foreignTableTypeName = inflection.tableType(foreignTable);
|
|
86
|
+
const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
|
|
87
|
+
const sqlFragment = connectionFilterResolve(fieldValue, foreignTableAlias, foreignTableFilterTypeName, queryBuilder);
|
|
88
|
+
return sqlFragment == null
|
|
89
|
+
? null
|
|
90
|
+
: sql.query `\
|
|
91
|
+
exists(
|
|
92
|
+
select 1 from ${sqlIdentifier} as ${foreignTableAlias}
|
|
93
|
+
where ${sqlKeysMatch} and
|
|
94
|
+
(${sqlFragment})
|
|
95
|
+
)`;
|
|
96
|
+
};
|
|
97
|
+
const resolveExists = ({ sourceAlias, fieldName, fieldValue, }) => {
|
|
98
|
+
if (fieldValue == null)
|
|
99
|
+
return null;
|
|
100
|
+
const { foreignTable, foreignKeyAttributes, keyAttributes } = forwardRelationSpecByFieldName[fieldName];
|
|
101
|
+
const foreignTableAlias = sql.identifier(Symbol());
|
|
102
|
+
const sqlIdentifier = sql.identifier(foreignTable.namespace.name, foreignTable.name);
|
|
103
|
+
const sqlKeysMatch = sql.query `(${sql.join(keyAttributes.map((key, i) => {
|
|
104
|
+
return sql.fragment `${sourceAlias}.${sql.identifier(key.name)} = ${foreignTableAlias}.${sql.identifier(foreignKeyAttributes[i].name)}`;
|
|
105
|
+
}), ') and (')})`;
|
|
106
|
+
const sqlSelectWhereKeysMatch = sql.query `select 1 from ${sqlIdentifier} as ${foreignTableAlias} where ${sqlKeysMatch}`;
|
|
107
|
+
return fieldValue === true
|
|
108
|
+
? sql.query `exists(${sqlSelectWhereKeysMatch})`
|
|
109
|
+
: sql.query `not exists(${sqlSelectWhereKeysMatch})`;
|
|
110
|
+
};
|
|
111
|
+
for (const spec of forwardRelationSpecs) {
|
|
112
|
+
const { constraint, foreignTable, keyAttributes } = spec;
|
|
113
|
+
const fieldName = inflection.singleRelationByKeys(keyAttributes, foreignTable, table, constraint);
|
|
114
|
+
const filterFieldName = inflection.filterSingleRelationFieldName(fieldName);
|
|
115
|
+
const foreignTableTypeName = inflection.tableType(foreignTable);
|
|
116
|
+
const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
|
|
117
|
+
const ForeignTableFilterType = connectionFilterType(newWithHooks, foreignTableFilterTypeName, foreignTable, foreignTableTypeName);
|
|
118
|
+
if (!ForeignTableFilterType)
|
|
119
|
+
continue;
|
|
120
|
+
addField(filterFieldName, `Filter by the object’s \`${fieldName}\` relation.`, ForeignTableFilterType, resolve, spec, `Adding connection filter forward relation field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`);
|
|
121
|
+
const keyIsNullable = !keyAttributes.every((attr) => attr.isNotNull);
|
|
122
|
+
if (keyIsNullable) {
|
|
123
|
+
const existsFieldName = inflection.filterForwardRelationExistsFieldName(fieldName);
|
|
124
|
+
addField(existsFieldName, `A related \`${fieldName}\` exists.`, GraphQLBoolean, resolveExists, spec, `Adding connection filter forward relation exists field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return fields;
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
exports.default = PgConnectionArgFilterForwardRelationsPlugin;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const PgConnectionArgFilterLogicalOperatorsPlugin = (builder) => {
|
|
4
|
+
builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
|
|
5
|
+
const { extend, graphql: { GraphQLList, GraphQLNonNull }, pgSql: sql, connectionFilterTypesByTypeName, connectionFilterResolve, connectionFilterRegisterResolver, } = build;
|
|
6
|
+
const { fieldWithHooks, scope: { isPgConnectionFilter }, Self, } = context;
|
|
7
|
+
if (!isPgConnectionFilter)
|
|
8
|
+
return fields;
|
|
9
|
+
connectionFilterTypesByTypeName[Self.name] = Self;
|
|
10
|
+
if (Object.keys(fields).length === 0) {
|
|
11
|
+
// Skip adding these operators if they would be the only fields
|
|
12
|
+
return fields;
|
|
13
|
+
}
|
|
14
|
+
const logicResolversByFieldName = {
|
|
15
|
+
and: (arr, sourceAlias, queryBuilder) => {
|
|
16
|
+
const sqlFragments = arr
|
|
17
|
+
.map((o) => connectionFilterResolve(o, sourceAlias, Self.name, queryBuilder))
|
|
18
|
+
.filter((x) => x != null);
|
|
19
|
+
return sqlFragments.length === 0
|
|
20
|
+
? null
|
|
21
|
+
: sql.query `(${sql.join(sqlFragments, ') and (')})`;
|
|
22
|
+
},
|
|
23
|
+
or: (arr, sourceAlias, queryBuilder) => {
|
|
24
|
+
const sqlFragments = arr
|
|
25
|
+
.map((o) => connectionFilterResolve(o, sourceAlias, Self.name, queryBuilder))
|
|
26
|
+
.filter((x) => x != null);
|
|
27
|
+
return sqlFragments.length === 0
|
|
28
|
+
? null
|
|
29
|
+
: sql.query `(${sql.join(sqlFragments, ') or (')})`;
|
|
30
|
+
},
|
|
31
|
+
not: (obj, sourceAlias, queryBuilder) => {
|
|
32
|
+
const sqlFragment = connectionFilterResolve(obj, sourceAlias, Self.name, queryBuilder);
|
|
33
|
+
return sqlFragment == null ? null : sql.query `not (${sqlFragment})`;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const logicalOperatorFields = {
|
|
37
|
+
and: fieldWithHooks('and', {
|
|
38
|
+
description: `Checks for all expressions in this list.`,
|
|
39
|
+
type: new GraphQLList(new GraphQLNonNull(Self)),
|
|
40
|
+
}, {
|
|
41
|
+
isPgConnectionFilterOperatorLogical: true,
|
|
42
|
+
}),
|
|
43
|
+
or: fieldWithHooks('or', {
|
|
44
|
+
description: `Checks for any expressions in this list.`,
|
|
45
|
+
type: new GraphQLList(new GraphQLNonNull(Self)),
|
|
46
|
+
}, {
|
|
47
|
+
isPgConnectionFilterOperatorLogical: true,
|
|
48
|
+
}),
|
|
49
|
+
not: fieldWithHooks('not', {
|
|
50
|
+
description: `Negates the expression.`,
|
|
51
|
+
type: Self,
|
|
52
|
+
}, {
|
|
53
|
+
isPgConnectionFilterOperatorLogical: true,
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => {
|
|
57
|
+
if (fieldValue == null)
|
|
58
|
+
return null;
|
|
59
|
+
return logicResolversByFieldName[fieldName](fieldValue, sourceAlias, queryBuilder);
|
|
60
|
+
};
|
|
61
|
+
for (const fieldName of Object.keys(logicResolversByFieldName)) {
|
|
62
|
+
connectionFilterRegisterResolver(Self.name, fieldName, resolve);
|
|
63
|
+
}
|
|
64
|
+
return extend(fields, logicalOperatorFields);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
exports.default = PgConnectionArgFilterLogicalOperatorsPlugin;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Plugin } from 'graphile-build';
|
|
2
|
+
import type { PgType, QueryBuilder, SQL } from 'graphile-build-pg';
|
|
3
|
+
import type { GraphQLInputType, GraphQLType } from 'graphql';
|
|
4
|
+
declare const PgConnectionArgFilterOperatorsPlugin: Plugin;
|
|
5
|
+
export interface OperatorSpec {
|
|
6
|
+
name?: string;
|
|
7
|
+
description: string;
|
|
8
|
+
resolveInput?: (input: unknown) => unknown;
|
|
9
|
+
resolveSql?: any;
|
|
10
|
+
resolveSqlIdentifier?: (sqlIdentifier: SQL, pgType: PgType, pgTypeModifier: number | null) => SQL;
|
|
11
|
+
resolveSqlValue?: (input: unknown, pgType: PgType, pgTypeModifier: number | null, resolveListItemSqlValue?: any) => SQL | null;
|
|
12
|
+
resolveType?: (fieldInputType: GraphQLInputType, rangeElementInputType: GraphQLInputType) => GraphQLType;
|
|
13
|
+
resolve: (sqlIdentifier: SQL, sqlValue: SQL, input: unknown, parentFieldName: string, queryBuilder: QueryBuilder) => SQL | null;
|
|
14
|
+
}
|
|
15
|
+
export default PgConnectionArgFilterOperatorsPlugin;
|