graphql-bookshelfjs
Version:
Using GraphQL with Bookshelf ORM
250 lines (233 loc) • 7.36 kB
JavaScript
;
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;
}
}
};