charlotte-graphql
Version:
Generates GraphQL type definitions and resolvers off of a concise spec.
179 lines (141 loc) • 4.24 kB
JavaScript
;
const _ = require('lodash');
const createId = require('uuid').v4;
module.exports = () => {
const store = {};
const self = {
store,
get: (type, arg) => {
const uuid = getId(arg);
const hyperedge = store[uuid];
const doesMatchType = hyperedge && (hyperedge._type.name === type.name);
return Promise.resolve(doesMatchType? hyperedge: null);
},
find: (type, args = {}) => {
const {
pagination = {},
conditions
} = args;
const {
limit = 20,
offset = 0
} = pagination;
const results = Object
.values(store)
.filter(hyperedge => doesMatch(type, hyperedge, conditions))
.slice(offset, limit + offset);
return Promise.resolve(results);
},
create: (type, args) => {
const uuid = createId();
const _created = Date.now();
const hyperedge = augment({
uuid,
_created,
_updated: _created,
_type: type,
_refs: []
}, args);
store[uuid] = hyperedge;
return Promise.resolve(hyperedge);
},
update: (type, { conditions, updates }) => {
const pagination = {
limit: Infinity
};
const _updated = Date.now();
return self.find(type, { conditions, pagination }).then(results => {
return Promise.all(
results.map(result => augment(result, Object.assign({ _updated }, updates)))
);
});
},
remove: (type, arg) => {
const uuid = getId(arg);
const hyperedge = store[uuid];
if (!hyperedge) {
return false;
}
delete store[uuid];
hyperedge._refs.forEach(ref => {
ref.hyperedge[ref.key] = null;
});
return Promise.resolve(true);
}
};
const augment = (hyperedge, args) => {
_.each(args, (v, k) => {
if (typeof v !== 'object') {
hyperedge[k] = v;
} else {
if (hyperedge[k]) {
hyperedge[k]._refs = hyperedge[k]._refs.filter(_ref => {
return (_ref.hyperedge.uuid === hyperedge.uuid) && (_ref.key === k);
});
}
hyperedge[k] = store[v.uuid];
store[v.uuid]._refs.push({
hyperedge,
key: k
});
}
});
return hyperedge;
};
return self;
};
const getId = node => typeof node === 'object'? node.uuid: node;
const operators = {
gt_: (arg, value) => value > +arg,
gte_: (arg, value) => value >= +arg,
lt_: (arg, value) => value < arg,
lte_: (arg, value) => value <= arg,
not_: (arg, value) => value !== arg,
search_: (arg, value) => value.toLowerCase().indexOf(arg.toLowerCase()) > -1
};
const doesMatch = (type, hyperedge, conditions = []) => {
if (type.isScalar) {
return conditions.some(v => v === hyperedge);
}
if (hyperedge._type.name !== type.name) {
return false;
}
if (conditions.length === 0) {
return true;
}
// OR conditions: just one needs to pass
return conditions.some(condition => {
// Test any operators
const passesOps = Object.keys(operators).every(o => {
const op = operators[o];
const args = condition[o];
if (!args) return true;
return Object.keys(args).every(k => {
return op(args[k], hyperedge[k]);
});
});
if (!passesOps) {
return false;
}
const conditionWithoutOperators = _.omit(condition, Object.keys(operators));
// AND conditions: if anything tests false, the whole condition fails.
return Object.entries(conditionWithoutOperators).every(([k, _conditions]) => {
const field = type.fields[k];
const v = hyperedge[k];
if (!field) {
throw new Error(`Field ${k} does not exist for type ${type.name}`);
}
if (field.reciprocalOf) {
return hyperedge._refs
// Reciprocal type must match with ref hyperedge type
.filter(_ref => {
return field.reciprocalOf[0].parentType.name === _ref.hyperedge._type.name;
})
.some(_ref => {
return doesMatch(_ref.hyperedge._type, _ref.hyperedge, _conditions);
});
}
return doesMatch(field.type, v, _conditions);
});
});
};