224 lines
5.7 KiB
JavaScript
224 lines
5.7 KiB
JavaScript
|
import { each, constant, find } from 'lodash';
|
||
|
import { map, max } from 'lodash/fp';
|
||
|
import {
|
||
|
switchCase, defaultCase, joinKey,
|
||
|
$, isNothing, isSomething,
|
||
|
} from '../common';
|
||
|
import {
|
||
|
isIndex, isRoot, isSingleRecord, isCollectionRecord,
|
||
|
isRecord, isaggregateGroup,
|
||
|
getFlattenedHierarchy,
|
||
|
} from './hierarchy';
|
||
|
import { all } from '../types';
|
||
|
import { BadRequestError } from '../common/errors';
|
||
|
|
||
|
export const createNodeErrors = {
|
||
|
indexCannotBeParent: 'Index template cannot be a parent',
|
||
|
allNonRootNodesMustHaveParent: 'Only the root node may have no parent',
|
||
|
indexParentMustBeRecordOrRoot: 'An index may only have a record or root as a parent',
|
||
|
aggregateParentMustBeAnIndex: 'aggregateGroup parent must be an index',
|
||
|
};
|
||
|
|
||
|
const pathRegxMaker = node => () => node.nodeKey().replace(/{id}/g, '[a-zA-Z0-9_-]+');
|
||
|
|
||
|
const nodeKeyMaker = node => () => switchCase(
|
||
|
|
||
|
[n => isRecord(n) && !isSingleRecord(n),
|
||
|
n => joinKey(
|
||
|
node.parent().nodeKey(),
|
||
|
node.collectionName,
|
||
|
`${n.nodeId}-{id}`,
|
||
|
)],
|
||
|
|
||
|
[isRoot,
|
||
|
constant('/')],
|
||
|
|
||
|
[defaultCase,
|
||
|
n => joinKey(node.parent().nodeKey(), n.name)],
|
||
|
|
||
|
)(node);
|
||
|
|
||
|
|
||
|
const validate = parent => (node) => {
|
||
|
if (isIndex(node)
|
||
|
&& isSomething(parent)
|
||
|
&& !isRoot(parent)
|
||
|
&& !isRecord(parent)) {
|
||
|
throw new BadRequestError(createNodeErrors.indexParentMustBeRecordOrRoot);
|
||
|
}
|
||
|
|
||
|
if (isaggregateGroup(node)
|
||
|
&& isSomething(parent)
|
||
|
&& !isIndex(parent)) {
|
||
|
throw new BadRequestError(createNodeErrors.aggregateParentMustBeAnIndex);
|
||
|
}
|
||
|
|
||
|
if (isNothing(parent) && !isRoot(node)) { throw new BadRequestError(createNodeErrors.allNonRootNodesMustHaveParent); }
|
||
|
|
||
|
return node;
|
||
|
};
|
||
|
|
||
|
const construct = parent => (node) => {
|
||
|
node.nodeKey = nodeKeyMaker(node);
|
||
|
node.pathRegx = pathRegxMaker(node);
|
||
|
node.parent = constant(parent);
|
||
|
node.isRoot = () => isNothing(parent)
|
||
|
&& node.name === 'root'
|
||
|
&& node.type === 'root';
|
||
|
if (isCollectionRecord(node)) {
|
||
|
node.collectionNodeKey = () => joinKey(
|
||
|
parent.nodeKey(), node.collectionName,
|
||
|
);
|
||
|
node.collectionPathRegx = () => joinKey(
|
||
|
parent.pathRegx(), node.collectionName,
|
||
|
);
|
||
|
}
|
||
|
return node;
|
||
|
};
|
||
|
|
||
|
const addToParent = (obj) => {
|
||
|
const parent = obj.parent();
|
||
|
if (isSomething(parent)) {
|
||
|
if (isIndex(obj))
|
||
|
// Q: why are indexes not children ?
|
||
|
// A: because they cannot have children of their own.
|
||
|
{ parent.indexes.push(obj); } else if (isaggregateGroup(obj)) { parent.aggregateGroups.push(obj); } else { parent.children.push(obj); }
|
||
|
|
||
|
if (isRecord(obj)) {
|
||
|
const defaultIndex = find(
|
||
|
parent.indexes,
|
||
|
i => i.name === `${parent.name}_index`,
|
||
|
);
|
||
|
if (defaultIndex) {
|
||
|
defaultIndex.allowedRecordNodeIds.push(obj.nodeId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return obj;
|
||
|
};
|
||
|
|
||
|
export const constructNode = (parent, obj) => $(obj, [
|
||
|
construct(parent),
|
||
|
validate(parent),
|
||
|
addToParent,
|
||
|
]);
|
||
|
|
||
|
const getNodeId = (parentNode) => {
|
||
|
// this case is handled better elsewhere
|
||
|
if (!parentNode) return null;
|
||
|
const findRoot = n => (isRoot(n) ? n : findRoot(n.parent()));
|
||
|
const root = findRoot(parentNode);
|
||
|
|
||
|
return ($(root, [
|
||
|
getFlattenedHierarchy,
|
||
|
map(n => n.nodeId),
|
||
|
max]) + 1);
|
||
|
};
|
||
|
|
||
|
export const constructHierarchy = (node, parent) => {
|
||
|
construct(parent)(node);
|
||
|
if (node.indexes) {
|
||
|
each(node.indexes,
|
||
|
child => constructHierarchy(child, node));
|
||
|
}
|
||
|
if (node.aggregateGroups) {
|
||
|
each(node.aggregateGroups,
|
||
|
child => constructHierarchy(child, node));
|
||
|
}
|
||
|
if (node.children && node.children.length > 0) {
|
||
|
each(node.children,
|
||
|
child => constructHierarchy(child, node));
|
||
|
}
|
||
|
if (node.fields) {
|
||
|
each(node.fields,
|
||
|
f => each(f.typeOptions, (val, key) => {
|
||
|
const def = all[f.type].optionDefinitions[key];
|
||
|
if (!def) {
|
||
|
// unknown typeOption
|
||
|
delete f.typeOptions[key];
|
||
|
} else {
|
||
|
f.typeOptions[key] = def.parse(val);
|
||
|
}
|
||
|
}));
|
||
|
}
|
||
|
return node;
|
||
|
};
|
||
|
|
||
|
|
||
|
export const getNewRootLevel = () => construct()({
|
||
|
name: 'root',
|
||
|
type: 'root',
|
||
|
children: [],
|
||
|
pathMaps: [],
|
||
|
indexes: [],
|
||
|
nodeId: 0,
|
||
|
});
|
||
|
|
||
|
const _getNewRecordTemplate = (parent, name, createDefaultIndex, isSingle) => {
|
||
|
const node = constructNode(parent, {
|
||
|
name,
|
||
|
type: 'record',
|
||
|
fields: [],
|
||
|
children: [],
|
||
|
validationRules: [],
|
||
|
nodeId: getNodeId(parent),
|
||
|
indexes: [],
|
||
|
allidsShardFactor: isRecord(parent) ? 1 : 64,
|
||
|
collectionName: '',
|
||
|
isSingle,
|
||
|
});
|
||
|
|
||
|
if (createDefaultIndex) {
|
||
|
const defaultIndex = getNewIndexTemplate(parent);
|
||
|
defaultIndex.name = `${name}_index`;
|
||
|
defaultIndex.allowedRecordNodeIds.push(node.nodeId);
|
||
|
}
|
||
|
|
||
|
return node;
|
||
|
};
|
||
|
|
||
|
export const getNewRecordTemplate = (parent, name = '', createDefaultIndex = true) => _getNewRecordTemplate(parent, name, createDefaultIndex, false);
|
||
|
|
||
|
export const getNewSingleRecordTemplate = parent => _getNewRecordTemplate(parent, '', false, true);
|
||
|
|
||
|
export const getNewIndexTemplate = (parent, type = 'ancestor') => constructNode(parent, {
|
||
|
name: '',
|
||
|
type: 'index',
|
||
|
map: 'return {...record};',
|
||
|
filter: '',
|
||
|
indexType: type,
|
||
|
getShardName: '',
|
||
|
getSortKey: 'record.id',
|
||
|
aggregateGroups: [],
|
||
|
allowedRecordNodeIds: [],
|
||
|
nodeId: getNodeId(parent),
|
||
|
});
|
||
|
|
||
|
export const getNewAggregateGroupTemplate = index => constructNode(index, {
|
||
|
name: '',
|
||
|
type: 'aggregateGroup',
|
||
|
groupBy: '',
|
||
|
aggregates: [],
|
||
|
condition: '',
|
||
|
nodeId: getNodeId(index),
|
||
|
});
|
||
|
|
||
|
export const getNewAggregateTemplate = (set) => {
|
||
|
const aggregatedValue = {
|
||
|
name: '',
|
||
|
aggregatedValue: '',
|
||
|
};
|
||
|
set.aggregates.push(aggregatedValue);
|
||
|
return aggregatedValue;
|
||
|
};
|
||
|
|
||
|
export default {
|
||
|
getNewRootLevel,
|
||
|
getNewRecordTemplate,
|
||
|
getNewIndexTemplate,
|
||
|
createNodeErrors,
|
||
|
constructHierarchy,
|
||
|
getNewAggregateGroupTemplate,
|
||
|
getNewAggregateTemplate,
|
||
|
};
|