graphql-compose-connection
Version:
Plugin for `graphql-compose` which provide a connection resolver for types.
310 lines • 13.7 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findSortConfig = exports.emptyConnection = exports.prepareLimitSkipFallback = exports.prepareRawQuery = exports.preparePageInfo = exports.prepareConnectionResolver = void 0;
const graphql_compose_1 = require("graphql-compose");
const connectionType_1 = require("./types/connectionType");
const sortInputType_1 = require("./types/sortInputType");
const cursor_1 = require("./cursor");
function prepareConnectionResolver(tc, opts) {
if (!(tc instanceof graphql_compose_1.ObjectTypeComposer)) {
throw new Error(`First arg for prepareConnectionResolver() should be instance of ObjectTypeComposer but received: ${graphql_compose_1.inspect(tc)}`);
}
if (!opts.countResolver || !(opts.countResolver instanceof graphql_compose_1.Resolver)) {
throw new Error(`Option 'opts.countResolver' must be a Resolver instance. Received ${graphql_compose_1.inspect(opts.countResolver)}`);
}
const countResolver = opts.countResolver;
const countResolve = countResolver.getResolve();
if (!opts.findManyResolver || !(opts.findManyResolver instanceof graphql_compose_1.Resolver)) {
throw new Error(`Option 'opts.findManyResolver' must be a Resolver instance. Received ${graphql_compose_1.inspect(opts.findManyResolver)}`);
}
const findManyResolver = opts.findManyResolver;
const findManyResolve = findManyResolver.getResolve();
const additionalArgs = {};
if (findManyResolver.hasArg('filter')) {
const filter = findManyResolver.getArg('filter');
if (filter) {
additionalArgs.filter = filter;
}
}
const sortEnumType = sortInputType_1.prepareSortType(tc, opts);
const firstField = sortEnumType.getFieldNames()[0];
const defaultValue = firstField && sortEnumType.getField(firstField).value;
const resolverName = opts.name || 'connection';
return tc.schemaComposer.createResolver({
type: connectionType_1.prepareConnectionType(tc, resolverName, opts.edgeTypeName, opts.edgeFields),
name: resolverName,
kind: 'query',
args: Object.assign(Object.assign({ first: {
type: 'Int',
description: 'Forward pagination argument for returning at most first edges',
}, after: {
type: 'String',
description: 'Forward pagination argument for returning at most first edges',
}, last: {
type: 'Int',
description: 'Backward pagination argument for returning at most last edges',
}, before: {
type: 'String',
description: 'Backward pagination argument for returning at most last edges',
} }, additionalArgs), { sort: {
type: sortEnumType,
defaultValue,
description: 'Sort argument for data ordering',
} }),
resolve(resolveParams) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
let countPromise;
let findManyPromise;
const { projection = {}, args, rawQuery } = resolveParams;
const findManyParams = Object.assign({}, resolveParams);
let first = parseInt(args.first, 10) || 0;
if (first < 0) {
throw new Error('Argument `first` should be non-negative number.');
}
const last = parseInt(args.last, 10) || 0;
if (last < 0) {
throw new Error('Argument `last` should be non-negative number.');
}
const countParams = Object.assign(Object.assign({}, resolveParams), { rawQuery, args: {
filter: Object.assign({}, resolveParams.args.filter),
} });
if (projection.count) {
countPromise = countResolve(countParams);
}
else if (!first && last) {
countPromise = countResolve(countParams);
}
else {
countPromise = Promise.resolve(0);
}
if (projection === null || projection === void 0 ? void 0 : projection.edges) {
const { edges } = projection, projectionWithoutEdges = __rest(projection, ["edges"]);
const extraProjection = {};
if (opts.edgeFields) {
Object.keys(opts.edgeFields).forEach((extraKey) => {
if (projection.edges[extraKey]) {
extraProjection[extraKey] = projection.edges[extraKey];
}
});
}
findManyParams.projection = Object.assign(Object.assign(Object.assign({}, projectionWithoutEdges), (_a = projection === null || projection === void 0 ? void 0 : projection.edges) === null || _a === void 0 ? void 0 : _a.node), extraProjection);
}
else {
findManyParams.projection = Object.assign({}, projection);
}
const sortConfig = findSortConfig(opts.sort, args.sort);
if (sortConfig) {
prepareRawQuery(resolveParams, sortConfig);
}
if (!first && last) {
const filteredCountParams = Object.assign(Object.assign({}, resolveParams), { args: {
filter: Object.assign({}, resolveParams.args.filter),
} });
first = yield countResolve(filteredCountParams);
first = parseInt(first, 10) || 0;
}
let limit = last || first || opts.defaultLimit || 20;
let skip = last > 0 ? first - last : 0;
let prepareCursorData;
if (sortConfig) {
findManyParams.rawQuery = resolveParams.rawQuery;
sortConfig.cursorFields.forEach((fieldName) => {
findManyParams.projection[fieldName] = true;
});
prepareCursorData = (record) => {
const result = {};
sortConfig.cursorFields.forEach((fieldName) => {
result[fieldName] = record[fieldName];
});
return result;
};
}
else {
[limit, skip] = prepareLimitSkipFallback(resolveParams, limit, skip);
let skipIdx = -1;
prepareCursorData = () => {
skipIdx += 1;
return skip + skipIdx;
};
}
findManyParams.args.limit = limit + 1;
if (skip > 0) {
findManyParams.args.skip = skip;
}
resolveParams.findManyResolveParams = findManyParams;
resolveParams.countResolveParams = countParams;
if (projection.count && Object.keys(projection).length === 1) {
findManyPromise = Promise.resolve([]);
}
else {
findManyPromise = findManyResolve(findManyParams);
}
return Promise.all([findManyPromise, countPromise])
.then(([recordList, count]) => {
const edges = recordList.map((record) => {
const edge = {
cursor: cursor_1.dataToCursor(prepareCursorData(record)),
node: opts.edgeFields ? record.node : record,
};
if (opts.edgeFields) {
Object.keys(opts.edgeFields).forEach((field) => {
edge[field] = record[field];
});
}
return edge;
});
return [edges, count];
})
.then(([edges, count]) => {
const result = emptyConnection();
result.edges = edges.length > limit ? edges.slice(0, limit) : edges;
result.pageInfo = preparePageInfo(edges, args, limit, skip);
result.count = count;
return result;
});
});
},
});
}
exports.prepareConnectionResolver = prepareConnectionResolver;
function preparePageInfo(edges, args, limit, skip) {
const pageInfo = {
startCursor: '',
endCursor: '',
hasPreviousPage: false,
hasNextPage: false,
};
const hasExtraRecords = edges.length > limit;
if (edges.length > 0 && limit > 0) {
pageInfo.startCursor = edges[0].cursor;
if (hasExtraRecords) {
pageInfo.endCursor = edges[limit - 1].cursor;
}
else {
pageInfo.endCursor = edges[edges.length - 1].cursor;
}
pageInfo.hasPreviousPage = skip > 0 || !!args.after;
pageInfo.hasNextPage = hasExtraRecords || !!args.before;
}
return pageInfo;
}
exports.preparePageInfo = preparePageInfo;
function prepareRawQuery(rp, sortConfig) {
var _a, _b;
if (!rp.rawQuery) {
rp.rawQuery = {};
}
const beginCursorData = cursor_1.cursorToData((_a = rp === null || rp === void 0 ? void 0 : rp.args) === null || _a === void 0 ? void 0 : _a.after);
if (beginCursorData) {
const r = sortConfig.afterCursorQuery(rp.rawQuery, beginCursorData, rp);
if (r !== undefined) {
rp.rawQuery = r;
}
}
const endCursorData = cursor_1.cursorToData((_b = rp === null || rp === void 0 ? void 0 : rp.args) === null || _b === void 0 ? void 0 : _b.before);
if (endCursorData) {
const r = sortConfig.beforeCursorQuery(rp.rawQuery, endCursorData, rp);
if (r !== undefined) {
rp.rawQuery = r;
}
}
}
exports.prepareRawQuery = prepareRawQuery;
function prepareLimitSkipFallback(rp, limit, skip) {
var _a, _b;
let newLimit = limit;
let newSkip = skip;
let beforeSkip = 0;
let afterSkip = 0;
if ((_a = rp === null || rp === void 0 ? void 0 : rp.args) === null || _a === void 0 ? void 0 : _a.before) {
const tmp = cursor_1.cursorToData(rp.args.before);
if (Number.isInteger(tmp)) {
beforeSkip = parseInt(tmp, 10);
}
}
if ((_b = rp === null || rp === void 0 ? void 0 : rp.args) === null || _b === void 0 ? void 0 : _b.after) {
const tmp = cursor_1.cursorToData(rp.args.after);
if (Number.isInteger(tmp)) {
afterSkip = parseInt(tmp, 10) + 1;
}
}
if (beforeSkip && afterSkip) {
const rangeLimit = beforeSkip - afterSkip;
if (rangeLimit < 0) {
newLimit = 0;
newSkip = skip + afterSkip;
}
else if (rangeLimit < limit) {
newLimit = rangeLimit;
newSkip = skip + beforeSkip - rangeLimit;
}
else {
newSkip = skip + afterSkip;
}
}
else if (beforeSkip) {
newSkip = beforeSkip - limit;
if (newSkip < 0) {
newSkip = 0;
newLimit = limit;
if (newLimit > beforeSkip) {
newLimit = beforeSkip;
}
}
}
else if (afterSkip) {
newSkip = afterSkip;
}
return [newLimit, newSkip];
}
exports.prepareLimitSkipFallback = prepareLimitSkipFallback;
function emptyConnection() {
return {
count: 0,
edges: [],
pageInfo: {
startCursor: '',
endCursor: '',
hasPreviousPage: false,
hasNextPage: false,
},
};
}
exports.emptyConnection = emptyConnection;
function findSortConfig(configs, val) {
for (const k in configs) {
if (configs[k].value === val) {
return configs[k];
}
}
const valStringified = JSON.stringify(val);
for (const k in configs) {
if (JSON.stringify(configs[k].value) === valStringified) {
return configs[k];
}
}
return undefined;
}
exports.findSortConfig = findSortConfig;
//# sourceMappingURL=connection.js.map
;