UNPKG

graphql-bookshelfjs

Version:
250 lines (233 loc) 7.36 kB
'use strict'; const DataLoader = require('dataloader'); const shimmer = require('shimmer'); /** * * @type {!object} */ let context; /** * * @param {string} key * @returns {(DataLoader|boolean)} */ function getLoader(key) { context.loaders = context.loaders || {}; if (context.loaders.hasOwnProperty(key)) { return context.loaders[key]; } return false; } /** * * @param {string} key * @param {DataLoader} loader */ function setLoader(key, loader) { context.loaders = context.loaders || {}; context.loaders[key] = loader; } /** * * @param {object} model * @param {string} targetIdAttribute * @param {string} relationType * @param {?object} queryBuilder * @returns {DataLoader} */ function modelLoader(model, targetIdAttribute, relationType, queryBuilder) { const loaderKey = [ model.prototype.tableName, targetIdAttribute, relationType, queryBuilder ? queryBuilder.toString() : '0', ].join('|'); let loader = getLoader(loaderKey); if (!loader) { const collection = (relationType === 'hasMany'); loader = new DataLoader((keys) => { return model.query((db) => { Object.assign(db, queryBuilder || {}); db.where(targetIdAttribute, 'in', keys); }).fetchAll().then((items) => { const byTargetId = {}; items.forEach((item) => { if (collection) { const key = item.attributes[targetIdAttribute]; byTargetId[key] = byTargetId[key] ? byTargetId[key] : []; byTargetId[key].push(item); } else byTargetId[item.attributes[targetIdAttribute]] = item; }); return keys.map((key) => { if (byTargetId.hasOwnProperty(key)) { return byTargetId[key]; } if (collection === true) { return []; } return null; }); }); }, { cache: false, }); setLoader(loaderKey, loader); } return loader; } /** * * @param {object} model * @param {string} joinTableName * @param {string} foreignKey * @param {string} otherKey * @param {string} targetIdAttribute * @param {?object} queryBuilder * @returns {DataLoader} */ function belongsToManyLoader(model, joinTableName, foreignKey, otherKey, targetIdAttribute, queryBuilder) { const loaderKey = [ model.prototype.tableName, joinTableName, foreignKey, otherKey, targetIdAttribute, queryBuilder ? queryBuilder.toString() : '0', ].join('|'); let loader = getLoader(loaderKey); if (!loader) { loader = new DataLoader((keys) => { return model.query((db) => { Object.assign(db, queryBuilder || {}); db.select([ `${model.prototype.tableName}.*`, `${joinTableName}.${foreignKey}`, `${joinTableName}.${otherKey}`, ]).innerJoin( joinTableName, `${model.prototype.tableName}.${targetIdAttribute}`, '=', `${joinTableName}.${otherKey}`) .where(`${joinTableName}.${foreignKey}`, 'in', keys); }) .fetchAll() .then((items) => { const byForeignKey = {}; items.forEach((item) => { const key = item.attributes[foreignKey]; byForeignKey[key] = byForeignKey[key] ? byForeignKey[key] : []; byForeignKey[key].push(item); }); return keys.map((key) => { if (byForeignKey.hasOwnProperty(key)) { return byForeignKey[key]; } return []; }); }); }, { cache: false, }); setLoader(loaderKey, loader); } return loader; } /** * * @param {object} target */ function belongsTo(target) { if (target.fetch.__wrapped) return; shimmer.wrap(target, 'fetch', (original) => { return function fetch() { const model = this.relatedData.target; const targetIdAttribute = this.relatedData.key('targetIdAttribute'); const parentFK = this.relatedData.key('parentFk'); const knex = this._knex; return modelLoader(model, targetIdAttribute, 'belongsTo', knex).load(parentFK); }; }); } /** * * @param {object} target */ function hasOne(target) { if (target.fetch.__wrapped) return; shimmer.wrap(target, 'fetch', (original) => { return function fetch() { const model = this.relatedData.target; const foreignKey = this.relatedData.key('foreignKey'); const parentFK = this.relatedData.key('parentFk'); const knex = this._knex; return modelLoader(model, foreignKey, 'hasOne', knex).load(parentFK); }; }); } /** * * @param {object} target */ function hasMany(target) { if (target.fetch.__wrapped) return; shimmer.wrap(target, 'fetch', (original) => { return function fetch() { const model = this.relatedData.target; const foreignKey = this.relatedData.key('foreignKey'); const parentFK = this.relatedData.key('parentFk'); const knex = this._knex; return modelLoader(model, foreignKey, 'hasMany', knex).load(parentFK); }; }); } /** * * @param {object} target */ function belongsToMany(target) { if (target.fetch.__wrapped) return; shimmer.wrap(target, 'fetch', (original) => { return function fetch() { const model = this.relatedData.target; const joinTableName = this.relatedData.key('joinTableName') || [this.tableName(), this.relatedData.key('parentTableName')].sort().join('_'); const foreignKey = this.relatedData.key('foreignKey'); const otherKey = this.relatedData.key('otherKey'); const targetIdAttribute = this.relatedData.key('targetIdAttribute'); const parentFK = this.relatedData.key('parentFk'); const knex = this._knex; return belongsToManyLoader(model, joinTableName, foreignKey, otherKey, targetIdAttribute, knex) .load(parentFK); }; }); } /** * * @param {object} target */ module.exports = function loaders(target) { context = this; context.__bookshelfResolver = context.__bookshelfResolver || {}; context = context.__bookshelfResolver; if (target.relatedData) { switch (target.relatedData.type) { case 'belongsTo': belongsTo(target); break; case 'belongsToMany': belongsToMany(target); break; case 'hasMany': hasMany(target); break; case 'hasOne': hasOne(target); break; default: break; } } };