@maktouch/graphql-directive-connection
Version:
Generate relay connections by marking fields with a @connection directive.
166 lines (165 loc) • 6.78 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const schema_1 = require("@graphql-tools/schema");
const utils_1 = require("@graphql-tools/utils");
const wrap_1 = require("@graphql-tools/wrap");
const graphql_1 = require("graphql");
function connectionDirective(directiveName, options) {
return {
connectionDirectiveTypeDefs: `directive @${directiveName} on FIELD_DEFINITION`,
connectionDirectiveTransform: (schema) => {
const newTypeDefs = [];
const foundTypes = {};
const connectionTypes = {};
const markedLocations = {};
// variables for cacheControl:
const connectionTypeGreatestMaxAge = {};
// Perform visitations:
const fieldVisitor = (fieldConfig, fieldName, typeName) => {
var _a;
const directives = utils_1.getDirectives(schema, fieldConfig);
for (const directive of directives) {
if (directive.name === directiveName) {
const baseName = getBaseType(fieldConfig.type.toString());
connectionTypes[baseName] = true;
markedLocations[`${typeName}.${fieldName}`] =
baseName + 'Connection';
// fieldConfig.type = makeConnectionType(fieldConfig.type) // does not work
// return fieldConfig
}
if (directive.name === 'cacheControl') {
const maxAge = (_a = directive.args) === null || _a === void 0 ? void 0 : _a.maxAge;
if (typeof maxAge === 'number') {
const baseName = getBaseType(fieldConfig.type.toString());
if (!connectionTypeGreatestMaxAge.hasOwnProperty(baseName) ||
maxAge > connectionTypeGreatestMaxAge[baseName]) {
connectionTypeGreatestMaxAge[baseName] = maxAge;
}
}
}
}
return undefined;
};
utils_1.mapSchema(schema, {
[utils_1.MapperKind.TYPE]: (type) => {
foundTypes[type.name] = type;
return undefined;
},
[utils_1.MapperKind.INTERFACE_FIELD]: fieldVisitor,
[utils_1.MapperKind.OBJECT_FIELD]: fieldVisitor,
});
// Construct new types:
if (!foundTypes['PageInfo']) {
newTypeDefs.push(`
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
`);
}
for (const name of Object.keys(connectionTypes)) {
// This applies the cacheControl to Edge type and edges, pageInfo fields
// The cacheControl is not applied to a Connection and Node types
// to comply with GraphQL List cacheControl behavior which has disabled cache by default
const maxAge = connectionTypeGreatestMaxAge[name];
const needsCacheControl = (options === null || options === void 0 ? void 0 : options.useCacheControl) && typeof maxAge === 'number';
const cacheControl = needsCacheControl
? ` @cacheControl(maxAge: ${maxAge})`
: '';
const newEdgeName = `${name}Edge`;
if (!foundTypes[newEdgeName]) {
newTypeDefs.push(`
type ${newEdgeName}${cacheControl} {
cursor: String!
node: ${name}
}
`);
}
const newConnectionName = `${name}Connection`;
if (!foundTypes[newConnectionName]) {
newTypeDefs.push(`
type ${newConnectionName} {
totalCount: Int!
edges: [${newEdgeName}]${cacheControl}
pageInfo: PageInfo!${cacheControl}
}
`);
}
}
schema = schema_1.mergeSchemas({
schemas: [schema],
typeDefs: newTypeDefs,
});
// Rename field types.
const transformer = (typeName, fieldName, fieldConfig) => {
var _a, _b;
const mark = markedLocations[`${typeName}.${fieldName}`];
if (mark) {
fieldConfig.type = makeConnectionType(fieldConfig.type);
fieldConfig.args = Object.assign(Object.assign({}, fieldConfig.args), makeConnectionArgs());
const remainingDirectives = (_b = (_a = fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.astNode) === null || _a === void 0 ? void 0 : _a.directives) === null || _b === void 0 ? void 0 : _b.filter((dir) => dir.name.value !== directiveName);
fieldConfig.astNode = Object.assign(Object.assign({}, fieldConfig.astNode), { directives: remainingDirectives });
return fieldConfig;
}
else
return undefined;
};
schema = wrap_1.wrapSchema({
schema,
transforms: [
new wrap_1.TransformInterfaceFields(transformer),
new wrap_1.TransformObjectFields(transformer),
],
});
return schema;
},
};
}
exports.default = connectionDirective;
function getBaseType(type) {
if (!type)
return '';
if (typeof type !== 'string')
return '';
return type
.replace(/:/g, '')
.replace(/\[/g, '')
.replace(/\]/g, '')
.replace(/!/g, '')
.replace(/@/g, '')
.trim();
}
function makeConnectionType(type) {
const formattedType = type.toString();
const baseName = getBaseType(formattedType);
return new graphql_1.GraphQLObjectType({
name: `${baseName}Connection`,
fields: {},
});
}
function makeConnectionArgs() {
return {
after: {
type: new graphql_1.GraphQLScalarType({
name: 'String',
}),
},
first: {
type: new graphql_1.GraphQLScalarType({
name: 'Int',
}),
},
before: {
type: new graphql_1.GraphQLScalarType({
name: 'String',
}),
},
last: {
type: new graphql_1.GraphQLScalarType({
name: 'Int',
}),
},
};
}