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.
- package/LICENSE +23 -0
- package/PgManyToManyRelationEdgeColumnsPlugin.d.ts +3 -0
- package/PgManyToManyRelationEdgeColumnsPlugin.js +60 -0
- package/PgManyToManyRelationEdgeTablePlugin.d.ts +3 -0
- package/PgManyToManyRelationEdgeTablePlugin.js +107 -0
- package/PgManyToManyRelationInflectionPlugin.d.ts +3 -0
- package/PgManyToManyRelationInflectionPlugin.js +41 -0
- package/PgManyToManyRelationPlugin.d.ts +3 -0
- package/PgManyToManyRelationPlugin.js +114 -0
- package/README.md +236 -0
- package/createManyToManyConnectionType.d.ts +16 -0
- package/createManyToManyConnectionType.js +134 -0
- package/esm/PgManyToManyRelationEdgeColumnsPlugin.js +58 -0
- package/esm/PgManyToManyRelationEdgeTablePlugin.js +105 -0
- package/esm/PgManyToManyRelationInflectionPlugin.js +39 -0
- package/esm/PgManyToManyRelationPlugin.js +109 -0
- package/esm/createManyToManyConnectionType.js +132 -0
- package/esm/index.js +28 -0
- package/esm/manyToManyRelationships.js +84 -0
- package/esm/types.js +1 -0
- package/index.d.ts +6 -0
- package/index.js +34 -0
- package/manyToManyRelationships.d.ts +4 -0
- package/manyToManyRelationships.js +86 -0
- package/package.json +51 -0
- package/types.d.ts +23 -0
- package/types.js +2 -0
|
@@ -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;
|