graphile-many-to-many 1.0.3

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.
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const hasNonNullKey = (row) => {
4
+ if (Array.isArray(row.__identifiers) && row.__identifiers.every((identifier) => identifier != null)) {
5
+ return true;
6
+ }
7
+ for (const key in row) {
8
+ if (Object.prototype.hasOwnProperty.call(row, key)) {
9
+ if ((key[0] !== '_' || key[1] !== '_') && row[key] !== null) {
10
+ return true;
11
+ }
12
+ }
13
+ }
14
+ return false;
15
+ };
16
+ const base64 = (value) => Buffer.from(String(value)).toString('base64');
17
+ const createManyToManyConnectionType = (relationship, build, options, leftTable) => {
18
+ const { leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint } = relationship;
19
+ const { newWithHooks, inflection, graphql: { GraphQLObjectType, GraphQLNonNull, GraphQLList }, getTypeByName, pgGetGqlTypeByTypeIdAndModifier, pgField, getSafeAliasFromResolveInfo, describePgEntity } = build;
20
+ const { pgForbidSetofFunctionsToReturnNull = false } = options;
21
+ const nullableIf = (condition, Type) => condition ? Type : new GraphQLNonNull(Type);
22
+ const Cursor = getTypeByName('Cursor');
23
+ const handleNullRow = pgForbidSetofFunctionsToReturnNull
24
+ ? (row) => row
25
+ : (row, identifiers) => {
26
+ if ((identifiers && hasNonNullKey(identifiers)) || hasNonNullKey(row)) {
27
+ return row;
28
+ }
29
+ return null;
30
+ };
31
+ const LeftTableType = pgGetGqlTypeByTypeIdAndModifier(leftTable.type.id, null);
32
+ if (!LeftTableType) {
33
+ throw new Error(`Could not determine type for table with id ${leftTable.type.id}`);
34
+ }
35
+ const TableType = pgGetGqlTypeByTypeIdAndModifier(rightTable.type.id, null);
36
+ if (!TableType) {
37
+ throw new Error(`Could not determine type for table with id ${rightTable.type.id}`);
38
+ }
39
+ const rightPrimaryKeyConstraint = rightTable.primaryKeyConstraint;
40
+ const rightPrimaryKeyAttributes = rightPrimaryKeyConstraint && rightPrimaryKeyConstraint.keyAttributes;
41
+ const junctionTypeName = inflection.tableType(junctionTable);
42
+ const EdgeType = newWithHooks(GraphQLObjectType, {
43
+ description: `A \`${TableType.name}\` edge in the connection, with data from \`${junctionTypeName}\`.`,
44
+ name: inflection.manyToManyRelationEdge(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint, LeftTableType.name),
45
+ fields: ({ fieldWithHooks }) => {
46
+ return {
47
+ cursor: fieldWithHooks('cursor', ({ addDataGenerator }) => {
48
+ addDataGenerator(() => ({
49
+ usesCursor: [true],
50
+ pgQuery: (queryBuilder) => {
51
+ if (rightPrimaryKeyAttributes) {
52
+ queryBuilder.selectIdentifiers(rightTable);
53
+ }
54
+ }
55
+ }));
56
+ return {
57
+ description: 'A cursor for use in pagination.',
58
+ type: Cursor,
59
+ resolve(data) {
60
+ return data.__cursor && base64(JSON.stringify(data.__cursor));
61
+ }
62
+ };
63
+ }, {
64
+ isCursorField: true
65
+ }),
66
+ node: pgField(build, fieldWithHooks, 'node', {
67
+ description: `The \`${TableType.name}\` at the end of the edge.`,
68
+ type: nullableIf(!pgForbidSetofFunctionsToReturnNull, TableType),
69
+ resolve(data, _args, _context, resolveInfo) {
70
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
71
+ const record = handleNullRow(data[safeAlias], data.__identifiers);
72
+ return record;
73
+ }
74
+ }, {}, false, {})
75
+ };
76
+ }
77
+ }, {
78
+ __origin: `Adding many-to-many edge type from ${describePgEntity(leftTable)} to ${describePgEntity(rightTable)} via ${describePgEntity(junctionTable)}.`,
79
+ isEdgeType: true,
80
+ isPgRowEdgeType: true,
81
+ isPgManyToManyEdgeType: true,
82
+ nodeType: TableType,
83
+ pgManyToManyRelationship: relationship
84
+ });
85
+ const PageInfo = getTypeByName(inflection.builtin('PageInfo'));
86
+ return newWithHooks(GraphQLObjectType, {
87
+ description: `A connection to a list of \`${TableType.name}\` values, with data from \`${junctionTypeName}\`.`,
88
+ name: inflection.manyToManyRelationConnection(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint, LeftTableType.name),
89
+ fields: ({ recurseDataGeneratorsForField, fieldWithHooks }) => {
90
+ recurseDataGeneratorsForField('pageInfo', true);
91
+ return {
92
+ nodes: pgField(build, fieldWithHooks, 'nodes', {
93
+ description: `A list of \`${TableType.name}\` objects.`,
94
+ type: new GraphQLNonNull(new GraphQLList(nullableIf(!pgForbidSetofFunctionsToReturnNull, TableType))),
95
+ resolve(data, _args, _context, resolveInfo) {
96
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
97
+ return data.data.map((entry) => {
98
+ const record = handleNullRow(entry[safeAlias], entry[safeAlias].__identifiers);
99
+ return record;
100
+ });
101
+ }
102
+ }, {}, false, {}),
103
+ edges: pgField(build, fieldWithHooks, 'edges', {
104
+ description: `A list of edges which contains the \`${TableType.name}\`, info from the \`${junctionTypeName}\`, and the cursor to aid in pagination.`,
105
+ type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(EdgeType))),
106
+ resolve(data, _args, _context, resolveInfo) {
107
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
108
+ return data.data.map((entry) => ({
109
+ ...entry,
110
+ ...entry[safeAlias]
111
+ }));
112
+ }
113
+ }, {}, false, {
114
+ hoistCursor: true
115
+ }),
116
+ pageInfo: PageInfo && {
117
+ description: 'Information to aid in pagination.',
118
+ type: new GraphQLNonNull(PageInfo),
119
+ resolve(data) {
120
+ return data;
121
+ }
122
+ }
123
+ };
124
+ }
125
+ }, {
126
+ __origin: `Adding many-to-many connection type from ${describePgEntity(leftTable)} to ${describePgEntity(rightTable)} via ${describePgEntity(junctionTable)}.`,
127
+ isConnectionType: true,
128
+ isPgRowConnectionType: true,
129
+ edgeType: EdgeType,
130
+ nodeType: TableType,
131
+ pgIntrospection: rightTable
132
+ });
133
+ };
134
+ exports.default = createManyToManyConnectionType;
@@ -0,0 +1,58 @@
1
+ const PgManyToManyRelationEdgeColumnsPlugin = (builder) => {
2
+ builder.hook('GraphQLObjectType:fields', (fields, build, context) => {
3
+ const { extend, pgGetGqlTypeByTypeIdAndModifier, pgSql: sql, pg2gql, graphql: { GraphQLString, GraphQLNonNull }, pgColumnFilter, inflection, pgOmit: omit, pgGetSelectValueForFieldAndTypeAndModifier: getSelectValueForFieldAndTypeAndModifier, describePgEntity } = build;
4
+ const { scope: { isPgManyToManyEdgeType, pgManyToManyRelationship }, fieldWithHooks } = context;
5
+ const nullableIf = (condition, Type) => condition ? Type : new GraphQLNonNull(Type);
6
+ if (!isPgManyToManyEdgeType || !pgManyToManyRelationship) {
7
+ return fields;
8
+ }
9
+ const { leftKeyAttributes, junctionTable, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, allowsMultipleEdgesToNode } = pgManyToManyRelationship;
10
+ if (allowsMultipleEdgesToNode) {
11
+ return fields;
12
+ }
13
+ return extend(fields, junctionTable.attributes.reduce((memo, attr) => {
14
+ if (!pgColumnFilter(attr, build, context))
15
+ return memo;
16
+ if (omit(attr, 'read'))
17
+ return memo;
18
+ // Skip left and right key attributes
19
+ if (junctionLeftKeyAttributes.map((a) => a.name).includes(attr.name))
20
+ return memo;
21
+ if (junctionRightKeyAttributes.map((a) => a.name).includes(attr.name))
22
+ return memo;
23
+ const fieldName = inflection.column(attr);
24
+ const ReturnType = pgGetGqlTypeByTypeIdAndModifier(attr.typeId, attr.typeModifier) || GraphQLString;
25
+ // Since we're ignoring multi-column keys, we can simplify here
26
+ const leftKeyAttribute = leftKeyAttributes[0];
27
+ const junctionLeftKeyAttribute = junctionLeftKeyAttributes[0];
28
+ const junctionRightKeyAttribute = junctionRightKeyAttributes[0];
29
+ const rightKeyAttribute = rightKeyAttributes[0];
30
+ const sqlSelectFrom = sql.fragment `select ${sql.identifier(attr.name)} from ${sql.identifier(junctionTable.namespace.name, junctionTable.name)}`;
31
+ const fieldConfig = fieldWithHooks(fieldName, (fieldContext) => {
32
+ const { type, typeModifier } = attr;
33
+ const { addDataGenerator } = fieldContext;
34
+ addDataGenerator((parsedResolveInfoFragment) => {
35
+ return {
36
+ pgQuery: (queryBuilder) => {
37
+ queryBuilder.select(getSelectValueForFieldAndTypeAndModifier(ReturnType, fieldContext, parsedResolveInfoFragment, sql.fragment `(${sqlSelectFrom} where ${sql.identifier(junctionRightKeyAttribute.name)} = ${queryBuilder.getTableAlias()}.${sql.identifier(rightKeyAttribute.name)} and ${sql.identifier(junctionLeftKeyAttribute.name)} = ${queryBuilder.parentQueryBuilder.parentQueryBuilder.getTableAlias()}.${sql.identifier(leftKeyAttribute.name)})`, type, typeModifier), fieldName);
38
+ }
39
+ };
40
+ });
41
+ return {
42
+ description: attr.description,
43
+ type: nullableIf(!attr.isNotNull && !attr.type.domainIsNotNull && !attr.tags.notNull, ReturnType),
44
+ resolve: (data) => {
45
+ return pg2gql(data[fieldName], attr.type);
46
+ }
47
+ };
48
+ }, {
49
+ isPgManyToManyRelationEdgeColumnField: true,
50
+ pgFieldIntrospection: attr
51
+ });
52
+ return extend(memo, {
53
+ [fieldName]: fieldConfig
54
+ }, `Adding field for ${describePgEntity(attr)}.`);
55
+ }, {}), `Adding columns to '${describePgEntity(junctionTable)}'`);
56
+ }, ['PgManyToManyRelationEdgeColumns']);
57
+ };
58
+ export default PgManyToManyRelationEdgeColumnsPlugin;
@@ -0,0 +1,105 @@
1
+ const PgManyToManyRelationEdgeTablePlugin = (builder, { pgSimpleCollections }) => {
2
+ builder.hook('GraphQLObjectType:fields', (fields, build, context) => {
3
+ const { extend, getTypeByName, pgGetGqlTypeByTypeIdAndModifier, graphql: { GraphQLNonNull, GraphQLList }, inflection, getSafeAliasFromResolveInfo, getSafeAliasFromAlias, pgQueryFromResolveData: queryFromResolveData, pgAddStartEndCursor: addStartEndCursor, pgSql: sql, describePgEntity } = build;
4
+ const { scope: { isPgManyToManyEdgeType, pgManyToManyRelationship }, fieldWithHooks, Self } = context;
5
+ if (!isPgManyToManyEdgeType || !pgManyToManyRelationship) {
6
+ return fields;
7
+ }
8
+ const { leftKeyAttributes, junctionLeftKeyAttributes, rightTable, rightKeyAttributes, junctionRightKeyAttributes, junctionTable, junctionRightConstraint, allowsMultipleEdgesToNode } = pgManyToManyRelationship;
9
+ if (!allowsMultipleEdgesToNode) {
10
+ return fields;
11
+ }
12
+ const JunctionTableType = pgGetGqlTypeByTypeIdAndModifier(junctionTable.type.id, null);
13
+ if (!JunctionTableType) {
14
+ throw new Error(`Could not determine type for table with id ${junctionTable.type.id}`);
15
+ }
16
+ const JunctionTableConnectionType = getTypeByName(inflection.connection(JunctionTableType.name));
17
+ const buildFields = (isConnection) => {
18
+ const fieldName = isConnection
19
+ ? inflection.manyRelationByKeys(junctionRightKeyAttributes, junctionTable, rightTable, junctionRightConstraint)
20
+ : inflection.manyRelationByKeysSimple(junctionRightKeyAttributes, junctionTable, rightTable, junctionRightConstraint);
21
+ const Type = isConnection ? JunctionTableConnectionType : JunctionTableType;
22
+ if (!Type) {
23
+ return undefined;
24
+ }
25
+ return {
26
+ [fieldName]: fieldWithHooks(fieldName, ({ getDataFromParsedResolveInfoFragment, addDataGenerator }) => {
27
+ const sqlFrom = sql.identifier(junctionTable.namespace.name, junctionTable.name);
28
+ const queryOptions = {
29
+ useAsterisk: junctionTable.canUseAsterisk,
30
+ withPagination: isConnection,
31
+ withPaginationAsFields: false,
32
+ asJsonAggregate: !isConnection
33
+ };
34
+ addDataGenerator((parsedResolveInfoFragment) => {
35
+ return {
36
+ pgQuery: (queryBuilder) => {
37
+ queryBuilder.select(() => {
38
+ const resolveData = getDataFromParsedResolveInfoFragment(parsedResolveInfoFragment, Type);
39
+ const junctionTableAlias = sql.identifier(Symbol());
40
+ const rightTableAlias = queryBuilder.getTableAlias();
41
+ const leftTableAlias = queryBuilder.parentQueryBuilder.parentQueryBuilder.getTableAlias();
42
+ const query = queryFromResolveData(sqlFrom, junctionTableAlias, resolveData, queryOptions, (innerQueryBuilder) => {
43
+ innerQueryBuilder.parentQueryBuilder = queryBuilder;
44
+ const junctionPrimaryKeyConstraint = junctionTable.primaryKeyConstraint;
45
+ const junctionPrimaryKeyAttributes = junctionPrimaryKeyConstraint && junctionPrimaryKeyConstraint.keyAttributes;
46
+ if (junctionPrimaryKeyAttributes) {
47
+ innerQueryBuilder.beforeLock('orderBy', () => {
48
+ // append order by primary key to the list of orders
49
+ if (!innerQueryBuilder.isOrderUnique(false)) {
50
+ innerQueryBuilder.data.cursorPrefix = ['primary_key_asc'];
51
+ junctionPrimaryKeyAttributes.forEach((attr) => {
52
+ innerQueryBuilder.orderBy(sql.fragment `${innerQueryBuilder.getTableAlias()}.${sql.identifier(attr.name)}`, true);
53
+ });
54
+ innerQueryBuilder.setOrderIsUnique();
55
+ }
56
+ });
57
+ }
58
+ junctionRightKeyAttributes.forEach((attr, i) => {
59
+ innerQueryBuilder.where(sql.fragment `${junctionTableAlias}.${sql.identifier(attr.name)} = ${rightTableAlias}.${sql.identifier(rightKeyAttributes[i].name)}`);
60
+ });
61
+ junctionLeftKeyAttributes.forEach((attr, i) => {
62
+ innerQueryBuilder.where(sql.fragment `${junctionTableAlias}.${sql.identifier(attr.name)} = ${leftTableAlias}.${sql.identifier(leftKeyAttributes[i].name)}`);
63
+ });
64
+ }, queryBuilder.context, queryBuilder.rootValue);
65
+ return sql.fragment `(${query})`;
66
+ }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias));
67
+ }
68
+ };
69
+ });
70
+ return {
71
+ description: `Reads and enables pagination through a set of \`${JunctionTableType.name}\`.`,
72
+ type: isConnection
73
+ ? new GraphQLNonNull(JunctionTableConnectionType)
74
+ : new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(JunctionTableType))),
75
+ args: {},
76
+ resolve: (data, _args, _context, resolveInfo) => {
77
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
78
+ if (isConnection) {
79
+ return addStartEndCursor(data[safeAlias]);
80
+ }
81
+ return data[safeAlias];
82
+ }
83
+ };
84
+ }, {
85
+ isPgFieldConnection: isConnection,
86
+ isPgFieldSimpleCollection: !isConnection,
87
+ isPgManyToManyRelationEdgeTableField: true,
88
+ pgFieldIntrospection: junctionTable
89
+ })
90
+ };
91
+ };
92
+ const simpleCollections = junctionRightConstraint.tags.simpleCollections ||
93
+ junctionTable.tags.simpleCollections ||
94
+ pgSimpleCollections;
95
+ const hasConnections = simpleCollections !== 'only';
96
+ const hasSimpleCollections = simpleCollections === 'only' || simpleCollections === 'both';
97
+ const connectionFields = hasConnections ? buildFields(true) : undefined;
98
+ const simpleCollectionFields = hasSimpleCollections ? buildFields(false) : undefined;
99
+ return extend(fields, {
100
+ ...(connectionFields || {}),
101
+ ...(simpleCollectionFields || {})
102
+ }, `Many-to-many relation edge table (${hasConnections ? 'connection' : 'simple collection'}) on ${Self.name} type for ${describePgEntity(junctionRightConstraint)}.`);
103
+ });
104
+ };
105
+ export default PgManyToManyRelationEdgeTablePlugin;
@@ -0,0 +1,39 @@
1
+ const PgManyToManyRelationInflectionPlugin = (builder) => {
2
+ builder.hook('inflection', (inflection) => {
3
+ const manyToManyRelationByKeys = function manyToManyRelationByKeys(_leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, _rightKeyAttributes, junctionTable, rightTable, _junctionLeftConstraint, junctionRightConstraint) {
4
+ if (junctionRightConstraint.tags.manyToManyFieldName) {
5
+ return junctionRightConstraint.tags.manyToManyFieldName;
6
+ }
7
+ return this.camelCase(`${this.pluralize(this._singularizedTableName(rightTable))}-by-${this._singularizedTableName(junctionTable)}-${[...junctionLeftKeyAttributes, ...junctionRightKeyAttributes]
8
+ .map((attr) => this.column(attr))
9
+ .join('-and-')}`);
10
+ };
11
+ const manyToManyRelationByKeysSimple = function manyToManyRelationByKeysSimple(_leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, _rightKeyAttributes, junctionTable, rightTable, _junctionLeftConstraint, junctionRightConstraint) {
12
+ if (junctionRightConstraint.tags.manyToManySimpleFieldName) {
13
+ return junctionRightConstraint.tags.manyToManySimpleFieldName;
14
+ }
15
+ return this.camelCase(`${this.pluralize(this._singularizedTableName(rightTable))}-by-${this._singularizedTableName(junctionTable)}-${[...junctionLeftKeyAttributes, ...junctionRightKeyAttributes]
16
+ .map((attr) => this.column(attr))
17
+ .join('-and-')}-list`);
18
+ };
19
+ const manyToManyRelationEdge = function manyToManyRelationEdge(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint, leftTableTypeName) {
20
+ const relationName = inflection.manyToManyRelationByKeys(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint);
21
+ return this.upperCamelCase(`${leftTableTypeName}-${relationName}-many-to-many-edge`);
22
+ };
23
+ const manyToManyRelationConnection = function manyToManyRelationConnection(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint, leftTableTypeName) {
24
+ const relationName = inflection.manyToManyRelationByKeys(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint, leftTableTypeName);
25
+ return this.upperCamelCase(`${leftTableTypeName}-${relationName}-many-to-many-connection`);
26
+ };
27
+ const manyToManyRelationSubqueryName = function manyToManyRelationSubqueryName(_leftKeyAttributes, _junctionLeftKeyAttributes, _junctionRightKeyAttributes, _rightKeyAttributes, junctionTable) {
28
+ return `many-to-many-subquery-by-${this._singularizedTableName(junctionTable)}`;
29
+ };
30
+ return Object.assign(inflection, {
31
+ manyToManyRelationByKeys,
32
+ manyToManyRelationByKeysSimple,
33
+ manyToManyRelationEdge,
34
+ manyToManyRelationConnection,
35
+ manyToManyRelationSubqueryName
36
+ });
37
+ });
38
+ };
39
+ export default PgManyToManyRelationInflectionPlugin;
@@ -0,0 +1,109 @@
1
+ import createManyToManyConnectionType from './createManyToManyConnectionType';
2
+ import manyToManyRelationships from './manyToManyRelationships';
3
+ const PgManyToManyRelationPlugin = (builder, options = {}) => {
4
+ const { pgSimpleCollections } = options;
5
+ builder.hook('GraphQLObjectType:fields', (fields, build, context) => {
6
+ const { extend, pgGetGqlTypeByTypeIdAndModifier, pgSql: sql, getSafeAliasFromResolveInfo, getSafeAliasFromAlias, graphql: { GraphQLNonNull, GraphQLList }, inflection, pgQueryFromResolveData: queryFromResolveData, pgAddStartEndCursor: addStartEndCursor, describePgEntity } = build;
7
+ const { scope: { isPgRowType, pgIntrospection: leftTable }, fieldWithHooks, Self } = context;
8
+ if (!isPgRowType || !leftTable || leftTable.kind !== 'class') {
9
+ return fields;
10
+ }
11
+ const relationships = manyToManyRelationships(leftTable, build);
12
+ const relatedFields = relationships.reduce((memo, relationship) => {
13
+ const { leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint } = relationship;
14
+ const RightTableType = pgGetGqlTypeByTypeIdAndModifier(rightTable.type.id, null);
15
+ if (!RightTableType) {
16
+ throw new Error(`Could not determine type for table with id ${rightTable.type.id}`);
17
+ }
18
+ const RightTableConnectionType = createManyToManyConnectionType(relationship, build, options, leftTable);
19
+ // Since we're ignoring multi-column keys, we can simplify here
20
+ const leftKeyAttribute = leftKeyAttributes[0];
21
+ const junctionLeftKeyAttribute = junctionLeftKeyAttributes[0];
22
+ const junctionRightKeyAttribute = junctionRightKeyAttributes[0];
23
+ const rightKeyAttribute = rightKeyAttributes[0];
24
+ let memoWithRelations = memo;
25
+ const makeFields = (isConnection) => {
26
+ const manyRelationFieldName = isConnection
27
+ ? inflection.manyToManyRelationByKeys(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint)
28
+ : inflection.manyToManyRelationByKeysSimple(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint);
29
+ memoWithRelations = extend(memoWithRelations, {
30
+ [manyRelationFieldName]: fieldWithHooks(manyRelationFieldName, ({ getDataFromParsedResolveInfoFragment, addDataGenerator }) => {
31
+ const sqlFrom = sql.identifier(rightTable.namespace.name, rightTable.name);
32
+ const queryOptions = {
33
+ useAsterisk: rightTable.canUseAsterisk,
34
+ withPagination: isConnection,
35
+ withPaginationAsFields: false,
36
+ asJsonAggregate: !isConnection
37
+ };
38
+ addDataGenerator((parsedResolveInfoFragment) => {
39
+ return {
40
+ pgQuery: (queryBuilder) => {
41
+ queryBuilder.select(() => {
42
+ const resolveData = getDataFromParsedResolveInfoFragment(parsedResolveInfoFragment, isConnection ? RightTableConnectionType : RightTableType);
43
+ const rightTableAlias = sql.identifier(Symbol());
44
+ const leftTableAlias = queryBuilder.getTableAlias();
45
+ const query = queryFromResolveData(sqlFrom, rightTableAlias, resolveData, queryOptions, (innerQueryBuilder) => {
46
+ innerQueryBuilder.parentQueryBuilder = queryBuilder;
47
+ const rightPrimaryKeyConstraint = rightTable.primaryKeyConstraint;
48
+ const rightPrimaryKeyAttributes = rightPrimaryKeyConstraint && rightPrimaryKeyConstraint.keyAttributes;
49
+ if (rightPrimaryKeyAttributes) {
50
+ innerQueryBuilder.beforeLock('orderBy', () => {
51
+ // append order by primary key to the list of orders
52
+ if (!innerQueryBuilder.isOrderUnique(false)) {
53
+ innerQueryBuilder.data.cursorPrefix = ['primary_key_asc'];
54
+ rightPrimaryKeyAttributes.forEach((attr) => {
55
+ innerQueryBuilder.orderBy(sql.fragment `${innerQueryBuilder.getTableAlias()}.${sql.identifier(attr.name)}`, true);
56
+ });
57
+ innerQueryBuilder.setOrderIsUnique();
58
+ }
59
+ });
60
+ }
61
+ const subqueryName = inflection.manyToManyRelationSubqueryName(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint);
62
+ const subqueryBuilder = innerQueryBuilder.buildNamedChildSelecting(subqueryName, sql.identifier(junctionTable.namespace.name, junctionTable.name), sql.identifier(junctionRightKeyAttribute.name));
63
+ subqueryBuilder.where(sql.fragment `${sql.identifier(junctionLeftKeyAttribute.name)} = ${leftTableAlias}.${sql.identifier(leftKeyAttribute.name)}`);
64
+ innerQueryBuilder.where(() => sql.fragment `${rightTableAlias}.${sql.identifier(rightKeyAttribute.name)} in (${subqueryBuilder.build()})`);
65
+ }, queryBuilder.context, queryBuilder.rootValue);
66
+ return sql.fragment `(${query})`;
67
+ }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias));
68
+ }
69
+ };
70
+ });
71
+ return {
72
+ description: `Reads and enables pagination through a set of \`${RightTableType.name}\`.`,
73
+ type: isConnection
74
+ ? new GraphQLNonNull(RightTableConnectionType)
75
+ : new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(RightTableType))),
76
+ args: {},
77
+ resolve: (data, _args, _context, resolveInfo) => {
78
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
79
+ if (isConnection) {
80
+ return addStartEndCursor(data[safeAlias]);
81
+ }
82
+ return data[safeAlias];
83
+ }
84
+ };
85
+ }, {
86
+ isPgFieldConnection: isConnection,
87
+ isPgFieldSimpleCollection: !isConnection,
88
+ isPgManyToManyRelationField: true,
89
+ pgFieldIntrospection: rightTable
90
+ })
91
+ }, `Many-to-many relation field (${isConnection ? 'connection' : 'simple collection'}) on ${Self.name} type for ${describePgEntity(junctionLeftConstraint)} and ${describePgEntity(junctionRightConstraint)}.`);
92
+ };
93
+ const simpleCollections = junctionRightConstraint.tags.simpleCollections ||
94
+ rightTable.tags.simpleCollections ||
95
+ pgSimpleCollections;
96
+ const hasConnections = simpleCollections !== 'only';
97
+ const hasSimpleCollections = simpleCollections === 'only' || simpleCollections === 'both';
98
+ if (hasConnections) {
99
+ makeFields(true);
100
+ }
101
+ if (hasSimpleCollections) {
102
+ makeFields(false);
103
+ }
104
+ return memoWithRelations;
105
+ }, {});
106
+ return extend(fields, relatedFields, `Adding many-to-many relations for ${Self.name}`);
107
+ });
108
+ };
109
+ export default PgManyToManyRelationPlugin;
@@ -0,0 +1,132 @@
1
+ const hasNonNullKey = (row) => {
2
+ if (Array.isArray(row.__identifiers) && row.__identifiers.every((identifier) => identifier != null)) {
3
+ return true;
4
+ }
5
+ for (const key in row) {
6
+ if (Object.prototype.hasOwnProperty.call(row, key)) {
7
+ if ((key[0] !== '_' || key[1] !== '_') && row[key] !== null) {
8
+ return true;
9
+ }
10
+ }
11
+ }
12
+ return false;
13
+ };
14
+ const base64 = (value) => Buffer.from(String(value)).toString('base64');
15
+ const createManyToManyConnectionType = (relationship, build, options, leftTable) => {
16
+ const { leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint } = relationship;
17
+ const { newWithHooks, inflection, graphql: { GraphQLObjectType, GraphQLNonNull, GraphQLList }, getTypeByName, pgGetGqlTypeByTypeIdAndModifier, pgField, getSafeAliasFromResolveInfo, describePgEntity } = build;
18
+ const { pgForbidSetofFunctionsToReturnNull = false } = options;
19
+ const nullableIf = (condition, Type) => condition ? Type : new GraphQLNonNull(Type);
20
+ const Cursor = getTypeByName('Cursor');
21
+ const handleNullRow = pgForbidSetofFunctionsToReturnNull
22
+ ? (row) => row
23
+ : (row, identifiers) => {
24
+ if ((identifiers && hasNonNullKey(identifiers)) || hasNonNullKey(row)) {
25
+ return row;
26
+ }
27
+ return null;
28
+ };
29
+ const LeftTableType = pgGetGqlTypeByTypeIdAndModifier(leftTable.type.id, null);
30
+ if (!LeftTableType) {
31
+ throw new Error(`Could not determine type for table with id ${leftTable.type.id}`);
32
+ }
33
+ const TableType = pgGetGqlTypeByTypeIdAndModifier(rightTable.type.id, null);
34
+ if (!TableType) {
35
+ throw new Error(`Could not determine type for table with id ${rightTable.type.id}`);
36
+ }
37
+ const rightPrimaryKeyConstraint = rightTable.primaryKeyConstraint;
38
+ const rightPrimaryKeyAttributes = rightPrimaryKeyConstraint && rightPrimaryKeyConstraint.keyAttributes;
39
+ const junctionTypeName = inflection.tableType(junctionTable);
40
+ const EdgeType = newWithHooks(GraphQLObjectType, {
41
+ description: `A \`${TableType.name}\` edge in the connection, with data from \`${junctionTypeName}\`.`,
42
+ name: inflection.manyToManyRelationEdge(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint, LeftTableType.name),
43
+ fields: ({ fieldWithHooks }) => {
44
+ return {
45
+ cursor: fieldWithHooks('cursor', ({ addDataGenerator }) => {
46
+ addDataGenerator(() => ({
47
+ usesCursor: [true],
48
+ pgQuery: (queryBuilder) => {
49
+ if (rightPrimaryKeyAttributes) {
50
+ queryBuilder.selectIdentifiers(rightTable);
51
+ }
52
+ }
53
+ }));
54
+ return {
55
+ description: 'A cursor for use in pagination.',
56
+ type: Cursor,
57
+ resolve(data) {
58
+ return data.__cursor && base64(JSON.stringify(data.__cursor));
59
+ }
60
+ };
61
+ }, {
62
+ isCursorField: true
63
+ }),
64
+ node: pgField(build, fieldWithHooks, 'node', {
65
+ description: `The \`${TableType.name}\` at the end of the edge.`,
66
+ type: nullableIf(!pgForbidSetofFunctionsToReturnNull, TableType),
67
+ resolve(data, _args, _context, resolveInfo) {
68
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
69
+ const record = handleNullRow(data[safeAlias], data.__identifiers);
70
+ return record;
71
+ }
72
+ }, {}, false, {})
73
+ };
74
+ }
75
+ }, {
76
+ __origin: `Adding many-to-many edge type from ${describePgEntity(leftTable)} to ${describePgEntity(rightTable)} via ${describePgEntity(junctionTable)}.`,
77
+ isEdgeType: true,
78
+ isPgRowEdgeType: true,
79
+ isPgManyToManyEdgeType: true,
80
+ nodeType: TableType,
81
+ pgManyToManyRelationship: relationship
82
+ });
83
+ const PageInfo = getTypeByName(inflection.builtin('PageInfo'));
84
+ return newWithHooks(GraphQLObjectType, {
85
+ description: `A connection to a list of \`${TableType.name}\` values, with data from \`${junctionTypeName}\`.`,
86
+ name: inflection.manyToManyRelationConnection(leftKeyAttributes, junctionLeftKeyAttributes, junctionRightKeyAttributes, rightKeyAttributes, junctionTable, rightTable, junctionLeftConstraint, junctionRightConstraint, LeftTableType.name),
87
+ fields: ({ recurseDataGeneratorsForField, fieldWithHooks }) => {
88
+ recurseDataGeneratorsForField('pageInfo', true);
89
+ return {
90
+ nodes: pgField(build, fieldWithHooks, 'nodes', {
91
+ description: `A list of \`${TableType.name}\` objects.`,
92
+ type: new GraphQLNonNull(new GraphQLList(nullableIf(!pgForbidSetofFunctionsToReturnNull, TableType))),
93
+ resolve(data, _args, _context, resolveInfo) {
94
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
95
+ return data.data.map((entry) => {
96
+ const record = handleNullRow(entry[safeAlias], entry[safeAlias].__identifiers);
97
+ return record;
98
+ });
99
+ }
100
+ }, {}, false, {}),
101
+ edges: pgField(build, fieldWithHooks, 'edges', {
102
+ description: `A list of edges which contains the \`${TableType.name}\`, info from the \`${junctionTypeName}\`, and the cursor to aid in pagination.`,
103
+ type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(EdgeType))),
104
+ resolve(data, _args, _context, resolveInfo) {
105
+ const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
106
+ return data.data.map((entry) => ({
107
+ ...entry,
108
+ ...entry[safeAlias]
109
+ }));
110
+ }
111
+ }, {}, false, {
112
+ hoistCursor: true
113
+ }),
114
+ pageInfo: PageInfo && {
115
+ description: 'Information to aid in pagination.',
116
+ type: new GraphQLNonNull(PageInfo),
117
+ resolve(data) {
118
+ return data;
119
+ }
120
+ }
121
+ };
122
+ }
123
+ }, {
124
+ __origin: `Adding many-to-many connection type from ${describePgEntity(leftTable)} to ${describePgEntity(rightTable)} via ${describePgEntity(junctionTable)}.`,
125
+ isConnectionType: true,
126
+ isPgRowConnectionType: true,
127
+ edgeType: EdgeType,
128
+ nodeType: TableType,
129
+ pgIntrospection: rightTable
130
+ });
131
+ };
132
+ export default createManyToManyConnectionType;
package/esm/index.js ADDED
@@ -0,0 +1,28 @@
1
+ import pkg from '../package.json';
2
+ import PgManyToManyRelationEdgeColumnsPlugin from './PgManyToManyRelationEdgeColumnsPlugin';
3
+ import PgManyToManyRelationEdgeTablePlugin from './PgManyToManyRelationEdgeTablePlugin';
4
+ import PgManyToManyRelationInflectionPlugin from './PgManyToManyRelationInflectionPlugin';
5
+ import PgManyToManyRelationPlugin from './PgManyToManyRelationPlugin';
6
+ const PgManyToManyPlugin = (builder, options = {}) => {
7
+ builder.hook('build', (build) => {
8
+ // Check dependencies
9
+ if (!build.versions) {
10
+ throw new Error(`Plugin ${pkg.name}@${pkg.version} requires graphile-build@^4.1.0 in order to check dependencies (current version: ${build.graphileBuildVersion})`);
11
+ }
12
+ const depends = (name, range) => {
13
+ if (!build.hasVersion(name, range)) {
14
+ throw new Error(`Plugin ${pkg.name}@${pkg.version} requires ${name}@${range} (${build.versions[name] ? `current version: ${build.versions[name]}` : 'not found'})`);
15
+ }
16
+ };
17
+ depends('graphile-build-pg', '^4.5.0');
18
+ // Register this plugin
19
+ build.versions = build.extend(build.versions, { [pkg.name]: pkg.version });
20
+ return build;
21
+ });
22
+ PgManyToManyRelationInflectionPlugin(builder, options);
23
+ PgManyToManyRelationPlugin(builder, options);
24
+ PgManyToManyRelationEdgeColumnsPlugin(builder, options);
25
+ PgManyToManyRelationEdgeTablePlugin(builder, options);
26
+ };
27
+ export { PgManyToManyPlugin };
28
+ export default PgManyToManyPlugin;