2017-02-02 11:56:52 +09:00
|
|
|
import gql from 'graphql-tag';
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
export const Fragments = {};
|
2017-06-17 15:24:53 +09:00
|
|
|
export const FragmentsExtensions = {}; // will be used on startup
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
/*
|
|
|
|
|
2017-08-20 15:24:13 +09:00
|
|
|
Get a fragment's name from its text
|
|
|
|
|
|
|
|
*/
|
|
|
|
export const extractFragmentName = fragmentText => fragmentText.match(/fragment (.*) on/)[1];
|
|
|
|
|
2017-12-28 11:31:55 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Get a query resolver's name from its text
|
|
|
|
|
|
|
|
*/
|
|
|
|
export const extractResolverName = resolverText => resolverText.trim().substr(0, resolverText.trim().indexOf('{'));
|
|
|
|
|
|
|
|
|
2017-08-20 15:24:13 +09:00
|
|
|
/*
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
Register a fragment, including its text, the text of its subfragments, and the fragment object
|
|
|
|
|
|
|
|
*/
|
2017-08-25 11:15:51 +02:00
|
|
|
export const registerFragment = fragmentTextSource => {
|
2017-08-25 11:10:29 +02:00
|
|
|
// remove comments
|
|
|
|
const fragmentText = fragmentTextSource.replace(/\#.*\n/g, '\n');
|
2017-02-02 13:24:29 +09:00
|
|
|
|
2017-02-02 11:56:52 +09:00
|
|
|
// extract name from fragment text
|
2017-08-20 15:24:13 +09:00
|
|
|
const fragmentName = extractFragmentName(fragmentText);
|
2017-09-04 19:56:17 +09:00
|
|
|
|
2017-02-02 11:56:52 +09:00
|
|
|
// extract subFragments from text
|
2017-08-25 11:10:29 +02:00
|
|
|
const matchedSubFragments = fragmentText.match(/\.{3}([_A-Za-z][_0-9A-Za-z]*)/g) || [];
|
2017-02-02 11:56:52 +09:00
|
|
|
const subFragments = _.unique(matchedSubFragments.map(f => f.replace('...', '')));
|
|
|
|
|
|
|
|
// register fragment
|
|
|
|
Fragments[fragmentName] = {
|
2017-09-13 10:09:54 +02:00
|
|
|
fragmentText
|
2017-02-02 11:56:52 +09:00
|
|
|
}
|
2017-09-04 19:56:17 +09:00
|
|
|
|
|
|
|
// also add subfragments if there are any
|
|
|
|
if(subFragments && subFragments.length) {
|
|
|
|
Fragments[fragmentName].subFragments = subFragments;
|
|
|
|
}
|
|
|
|
|
2017-01-30 19:46:48 +09:00
|
|
|
};
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Create gql fragment object from text and subfragments
|
|
|
|
|
|
|
|
*/
|
|
|
|
export const getFragmentObject = (fragmentText, subFragments) => {
|
|
|
|
// pad the literals array with line returns for each subFragments
|
2017-09-04 19:56:17 +09:00
|
|
|
const literals = subFragments ? [fragmentText, ...subFragments.map(x => '\n')] : [fragmentText];
|
2017-07-07 09:40:34 +09:00
|
|
|
|
|
|
|
// the gql function expects an array of literals as first argument, and then sub-fragments as other arguments
|
2017-09-04 19:56:17 +09:00
|
|
|
const gqlArguments = subFragments ? [literals, ...subFragments.map(subFragmentName => {
|
2017-07-07 09:40:34 +09:00
|
|
|
// return subfragment's gql fragment
|
2018-01-03 15:13:50 +09:00
|
|
|
if (!Fragments[subFragmentName] || !Fragments[subFragmentName].fragmentObject) {
|
2017-09-13 12:13:16 +02:00
|
|
|
throw new Error(`Subfragment “${subFragmentName}” of fragment “${extractFragmentName(fragmentText)}” has not been initialized yet.`);
|
|
|
|
}
|
2017-07-07 09:40:34 +09:00
|
|
|
return Fragments[subFragmentName].fragmentObject;
|
2017-09-04 19:56:17 +09:00
|
|
|
})] : [literals];
|
2017-07-07 09:40:34 +09:00
|
|
|
|
|
|
|
return gql.apply(null, gqlArguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
2017-07-25 18:37:23 +09:00
|
|
|
Create default "dumb" gql fragment object for a given collection
|
|
|
|
|
|
|
|
*/
|
2017-08-20 15:24:13 +09:00
|
|
|
export const getDefaultFragmentText = (collection, options = { onlyViewable: true }) => {
|
2017-07-25 18:37:23 +09:00
|
|
|
const schema = collection.simpleSchema()._schema;
|
2017-08-20 15:24:13 +09:00
|
|
|
const fieldNames = _.reject(_.keys(schema), fieldName => {
|
|
|
|
/*
|
|
|
|
|
|
|
|
Exclude a field from the default fragment if
|
2017-09-06 18:10:26 +02:00
|
|
|
1. it has a resolver and addOriginalField is false
|
2017-08-20 15:24:13 +09:00
|
|
|
2. it has $ in its name
|
|
|
|
3. it's not viewable (if onlyViewable option is true)
|
|
|
|
|
|
|
|
*/
|
|
|
|
const field = schema[fieldName];
|
2018-06-22 20:55:22 +09:00
|
|
|
// OpenCRUD backwards compatibility
|
2018-09-01 07:41:55 +09:00
|
|
|
return (field.resolveAs && !field.resolveAs.addOriginalField) || fieldName.includes('$') || fieldName.includes('.') || options.onlyViewable && !(field.canRead || field.viewableBy);
|
2017-08-20 15:24:13 +09:00
|
|
|
});
|
2017-07-25 18:37:23 +09:00
|
|
|
|
2017-09-25 22:08:17 +02:00
|
|
|
if (fieldNames.length) {
|
|
|
|
const fragmentText = `
|
|
|
|
fragment ${collection.options.collectionName}DefaultFragment on ${collection.typeName} {
|
|
|
|
${fieldNames.map(fieldName => {
|
|
|
|
return fieldName+'\n'
|
|
|
|
}).join('')}
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
return fragmentText;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-25 18:37:23 +09:00
|
|
|
|
|
|
|
}
|
2017-08-02 15:51:11 +09:00
|
|
|
export const getDefaultFragment = collection => {
|
2017-09-25 22:08:17 +02:00
|
|
|
const fragmentText = getDefaultFragmentText(collection);
|
|
|
|
return fragmentText ? gql`${fragmentText}` : null;
|
2017-08-02 15:51:11 +09:00
|
|
|
}
|
2017-07-25 18:37:23 +09:00
|
|
|
/*
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
Queue a fragment to be extended with additional properties.
|
|
|
|
|
|
|
|
Note: can be used even before the fragment has been registered.
|
|
|
|
|
|
|
|
*/
|
2017-02-02 13:24:29 +09:00
|
|
|
export const extendFragment = (fragmentName, newProperties) => {
|
2017-07-07 09:40:34 +09:00
|
|
|
FragmentsExtensions[fragmentName] = FragmentsExtensions[fragmentName] ? [...FragmentsExtensions[fragmentName], newProperties] : [newProperties];
|
2017-06-17 15:24:53 +09:00
|
|
|
}
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Perform fragment extension (called from initializeFragments()
|
|
|
|
|
|
|
|
Note: will call registerFragment again each time, resulting in multiple fragments
|
|
|
|
with the same name (but duplicate fragments warning is disabled).
|
|
|
|
|
|
|
|
*/
|
2017-06-17 15:24:53 +09:00
|
|
|
export const extendFragmentWithProperties = (fragmentName, newProperties) => {
|
2017-02-02 13:24:29 +09:00
|
|
|
const fragment = Fragments[fragmentName];
|
|
|
|
const fragmentEndPosition = fragment.fragmentText.lastIndexOf('}');
|
2017-09-04 19:56:17 +09:00
|
|
|
const newFragmentText = [
|
|
|
|
fragment.fragmentText.slice(0, fragmentEndPosition),
|
|
|
|
newProperties,
|
|
|
|
fragment.fragmentText.slice(fragmentEndPosition)
|
|
|
|
].join('');
|
2017-02-02 13:24:29 +09:00
|
|
|
registerFragment(newFragmentText);
|
|
|
|
}
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Remove a property from a fragment
|
|
|
|
|
|
|
|
Note: can only be called *after* a fragment is registered
|
|
|
|
|
|
|
|
*/
|
2017-05-14 11:08:07 +09:00
|
|
|
export const removeFromFragment = (fragmentName, propertyName) => {
|
|
|
|
const fragment = Fragments[fragmentName];
|
|
|
|
const newFragmentText = fragment.fragmentText.replace(propertyName, '');
|
|
|
|
registerFragment(newFragmentText);
|
|
|
|
}
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Get fragment name from fragment object
|
|
|
|
|
|
|
|
*/
|
2017-06-20 10:25:34 +09:00
|
|
|
export const getFragmentName = fragment => fragment && fragment.definitions[0] && fragment.definitions[0].name.value;
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
/*
|
2017-01-30 19:46:48 +09:00
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
Get actual gql fragment
|
2017-02-02 13:24:29 +09:00
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
*/
|
|
|
|
export const getFragment = fragmentName => {
|
|
|
|
if (!Fragments[fragmentName]) {
|
2017-09-13 10:09:54 +02:00
|
|
|
throw new Error(`Fragment "${fragmentName}" not registered.`);
|
|
|
|
}
|
|
|
|
if (!Fragments[fragmentName].fragmentObject) {
|
2018-07-27 10:17:53 +02:00
|
|
|
initializeFragments([fragmentName]);
|
2017-02-17 20:45:11 +09:00
|
|
|
}
|
2017-06-19 10:19:05 +09:00
|
|
|
// return fragment object created by gql
|
2017-07-07 09:40:34 +09:00
|
|
|
return Fragments[fragmentName].fragmentObject;
|
2017-06-19 10:19:05 +09:00
|
|
|
}
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
/*
|
2017-06-19 10:19:05 +09:00
|
|
|
|
2017-08-20 15:24:13 +09:00
|
|
|
Get gql fragment text
|
|
|
|
|
|
|
|
*/
|
|
|
|
export const getFragmentText = fragmentName => {
|
|
|
|
if (!Fragments[fragmentName]) {
|
|
|
|
throw new Error(`Fragment "${fragmentName}" not registered.`)
|
|
|
|
}
|
|
|
|
// return fragment object created by gql
|
|
|
|
return Fragments[fragmentName].fragmentText;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
2018-07-27 10:16:56 +02:00
|
|
|
Get names of non initialized fragments.
|
|
|
|
|
|
|
|
*/
|
|
|
|
export const getNonInitializedFragmentNames = () =>
|
|
|
|
_.keys(Fragments).filter(name => !Fragments[name].fragmentObject);
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
Perform all fragment extensions (called from routing)
|
2017-06-17 15:24:53 +09:00
|
|
|
|
2017-07-07 09:40:34 +09:00
|
|
|
*/
|
2018-07-27 10:16:56 +02:00
|
|
|
export const initializeFragments = (fragments = getNonInitializedFragmentNames()) => {
|
2017-09-04 19:56:17 +09:00
|
|
|
|
2017-09-13 12:13:16 +02:00
|
|
|
const errorFragmentKeys = [];
|
|
|
|
|
2017-09-04 19:56:17 +09:00
|
|
|
// extend fragment texts (if extended fragment exists)
|
2017-07-07 09:40:34 +09:00
|
|
|
_.forEach(FragmentsExtensions, (extensions, fragmentName) => {
|
2017-06-22 16:19:23 +09:00
|
|
|
if (Fragments[fragmentName]) {
|
2017-07-07 09:40:34 +09:00
|
|
|
extensions.forEach(newProperties => {
|
|
|
|
extendFragmentWithProperties(fragmentName, newProperties);
|
|
|
|
});
|
2017-06-22 16:19:23 +09:00
|
|
|
}
|
2017-06-17 15:24:53 +09:00
|
|
|
});
|
2017-09-04 19:56:17 +09:00
|
|
|
|
|
|
|
// create fragment objects
|
|
|
|
|
|
|
|
// initialize fragments *with no subfragments* first to avoid unresolved dependencies
|
2018-07-27 10:16:56 +02:00
|
|
|
const keysWithoutSubFragments = _.filter(fragments, fragmentName => !Fragments[fragmentName].subFragments);
|
2017-09-04 19:56:17 +09:00
|
|
|
_.forEach(keysWithoutSubFragments, fragmentName => {
|
|
|
|
const fragment = Fragments[fragmentName];
|
|
|
|
fragment.fragmentObject = getFragmentObject(fragment.fragmentText, fragment.subFragments)
|
|
|
|
});
|
|
|
|
|
|
|
|
// next, initialize fragments that *have* subfragments
|
|
|
|
const keysWithSubFragments = _.filter(_.keys(Fragments), fragmentName => !!Fragments[fragmentName].subFragments);
|
|
|
|
_.forEach(keysWithSubFragments, fragmentName => {
|
|
|
|
const fragment = Fragments[fragmentName];
|
2017-09-13 12:13:16 +02:00
|
|
|
try {
|
|
|
|
fragment.fragmentObject = getFragmentObject(fragment.fragmentText, fragment.subFragments);
|
|
|
|
} catch (error) {
|
|
|
|
// if fragment initialization triggers an error, store fragment and try again later
|
|
|
|
// common error causes include cross-dependencies
|
|
|
|
errorFragmentKeys.push(fragmentName);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// finally, try initializing any fragment that triggered an error again
|
|
|
|
_.forEach(errorFragmentKeys, fragmentName => {
|
|
|
|
const fragment = Fragments[fragmentName];
|
|
|
|
fragment.fragmentObject = getFragmentObject(fragment.fragmentText, fragment.subFragments);
|
2017-09-04 19:56:17 +09:00
|
|
|
});
|
|
|
|
|
2017-01-30 19:46:48 +09:00
|
|
|
}
|