budibase/packages/core/src/templateApi/createNodes.js

232 lines
5.8 KiB
JavaScript
Raw Normal View History

2019-09-28 06:28:11 +02:00
import { each, find } from 'lodash';
import { map, max, constant } from 'lodash/fp';
2019-07-15 08:12:52 +02:00
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.
2019-10-31 10:22:46 +01:00
{
parent.indexes.push(obj);
}
else if (isaggregateGroup(obj))
{
parent.aggregateGroups.push(obj);
} else {
parent.children.push(obj);
}
2019-07-15 08:12:52 +02:00
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,
};