UNPKG

@digicat/cf-graphql

Version:

Generate a GraphQL schema out of your Contentful space

123 lines (106 loc) 3.48 kB
'use strict'; const _get = require('lodash.get'); const chunk = require('lodash.chunk'); const qs = require('querystring'); const DataLoader = require('dataloader'); const warning = require('warning'); const INCLUDE_DEPTH = 1; const CHUNK_SIZE = 100; const DEFAULT_LIMIT = 50; const MAX_LIMIT = 1000; const FORBIDDEN_QUERY_PARAMS = ['skip', 'limit', 'include', 'content_type', 'locale']; module.exports = createEntryLoader; function createEntryLoader (http) { const loader = new DataLoader(load); const assets = {}; return { get: getOne, getMany: loader.loadMany.bind(loader), query: (ctId, args) => query(ctId, args).then(res => res.items), count: (ctId, args) => query(ctId, args).then(res => res.total), queryAll, getIncludedAsset: id => assets[id], getTimeline: () => http.timeline }; function load (ids) { // we need to chunk IDs and fire multiple requests so we don't produce URLs // that are too long (for the server to handle) const requests = chunk(ids, CHUNK_SIZE) .map(ids => http.get('/entries', { limit: CHUNK_SIZE, skip: 0, include: INCLUDE_DEPTH, 'sys.id[in]': ids.join(',') })); return Promise.all(requests) .then(responses => responses.reduce((acc, res) => { prime(res); _get(res, ['items'], []).forEach(e => acc[e.sys.id] = e); return acc; }, {})) .then(byId => ids.map(id => byId[id])); } function getOne (id, forcedCtId) { return loader.load(id) .then(res => { if (typeof res === 'undefined') { warning(true, `Cannot load #${id}, probably unpublished content`); return null; } const ctId = _get(res, ['sys', 'contentType', 'sys', 'id']); if (forcedCtId && ctId !== forcedCtId) { throw new Error('Does not match the forced Content Type ID.'); } else { return res; } }); } function query (ctId, {q = '', skip = 0, limit = DEFAULT_LIMIT} = {}) { const parsed = qs.parse(q); Object.keys(parsed).forEach(key => { if (FORBIDDEN_QUERY_PARAMS.includes(key)) { throw new Error(`Cannot use a query param named "${key}" here.`); } }); const params = Object.assign({ limit, skip, include: INCLUDE_DEPTH, content_type: ctId }, parsed); return http.get('/entries', params).then(prime); } function queryAll (ctId) { const paramsFor = page => ({ limit: MAX_LIMIT, skip: page*MAX_LIMIT, include: INCLUDE_DEPTH, content_type: ctId }); return http.get('/entries', paramsFor(0)) .then(firstResponse => { const length = Math.ceil(firstResponse.total/MAX_LIMIT)-1; const pages = Array.apply(null, {length}).map((_, i) => i+1); const requests = pages.map(page => http.get('/entries', paramsFor(page))); return Promise.all([Promise.resolve(firstResponse)].concat(requests)); }) .then(responses => responses.reduce((acc, res) => { prime(res); return res.items.reduce((acc, item) => { if (!acc.some(e => e.sys.id === item.sys.id)) { return acc.concat([item]); } else { return acc; } }, acc); }, [])); } function prime (res) { _get(res, ['items'], []) .concat(_get(res, ['includes', 'Entry'], [])) .forEach(e => loader.prime(e.sys.id, e)); _get(res, ['includes', 'Asset'], []) .forEach(a => assets[a.sys.id] = a); return res; } }