sails-arangojs
Version:
A sails-arangojs adapter for Sails / Waterline
367 lines (313 loc) • 12.1 kB
JavaScript
// ██████╗ ██╗ ██╗██╗██╗ ██████╗ ███████╗ ██████╗██╗ ██╗███████╗███╗ ███╗ █████╗
// ██╔══██╗██║ ██║██║██║ ██╔══██╗ ██╔════╝██╔════╝██║ ██║██╔════╝████╗ ████║██╔══██╗
// ██████╔╝██║ ██║██║██║ ██║ ██║ ███████╗██║ ███████║█████╗ ██╔████╔██║███████║
// ██╔══██╗██║ ██║██║██║ ██║ ██║ ╚════██║██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══██║
// ██████╔╝╚██████╔╝██║███████╗██████╔╝ ███████║╚██████╗██║ ██║███████╗██║ ╚═╝ ██║██║ ██║
// ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
//
// Build a schema object that is suitable for using in a Create Table query.
const _ = require('@sailshq/lodash');
const flaverr = require('flaverr');
module.exports = async function buildSchema(tableName, definition, collection) {
if (!definition || !tableName) {
throw new Error('Build Schema/Table Name requires a valid definition.');
}
const pk = definition.primaryKey;
let createdIndexes = [];
if (_.isObject(definition) && !definition.attributes) {
try {
const indexes = _.map(
definition,
(attribute, name) =>
new Promise(async (resolv) => {
const unique = Boolean(attribute.unique);
// attribute.unique, allowNull, etc
if (attribute && unique && !attribute.primaryKey) {
try {
const ind = await collection.ensureIndex({
fields: [`${name}`],
name: `${name}`,
unique: true,
type: 'persistent',
sparse: Boolean(!attribute.required),
});
} catch (error) {
resolv('');
}
resolv(`${name}`);
}
resolv('');
})
);
createdIndexes = await Promise.all(indexes);
} catch (error) {
flaverr(
{
code: 'E_BULDING_INDEXES',
message: `Could not build indexes for ${tableName}`,
},
error
);
}
}
try {
const indexes = _.map(
definition.attributes,
(attribute, name) =>
new Promise(async (resolv, reject) => {
const autoMigrations = attribute.autoMigrations || {};
const unique = Boolean(autoMigrations.unique);
// attribute.unique, allowNull, etc
if (attribute && unique && name !== pk) {
try {
await collection.ensureIndex({
fields: [`${name}`],
name: `${name}`,
unique: true,
type: 'persistent',
sparse: Boolean(!attribute.required),
});
} catch (error) {
resolv('');
}
resolv(`${name}`);
}
resolv('');
})
);
createdIndexes = await Promise.all(indexes);
} catch (error) {
flaverr(
{
code: 'E_BULDING_INDEXES',
message: `Could not build indexes for ${tableName}`,
},
error
);
}
const modelIndexes = await collection.indexes();
createdIndexes = createdIndexes.filter((i) => !!i);
// Delete indexes when they are removed from model
for (let fld of modelIndexes) {
if (fld.fields.length === 1) {
const indexfield = fld.fields.join('');
if (indexfield !== '_key') {
if (!createdIndexes.includes(indexfield)) {
await collection.dropIndex(fld.name);
}
}
} else {
const indexfield = fld.fields.join('');
if (indexfield !== '_from_to') {
await collection.dropIndex(fld);
}
}
}
// Build up a string of column attributes
// ENFORCE SCHEMA in DB
if (['strict', 'moderate', 'new'].includes(definition.schemaValidation)) {
const attributes = { ...definition.attributes };
delete attributes.createdAt;
delete attributes.updatedAt;
delete attributes.id;
delete attributes._key;
delete attributes._id;
let required = [];
let properties = {};
for (let fldName in attributes) {
let fldProps = {};
const attProps = attributes[fldName] || {};
if (
attProps.required ||
attProps.defaultsTo ||
typeof attProps.defaultsTo === 'number'
) {
required.push(fldName);
}
if (attProps.type === 'string' && !attProps.allowNull) {
fldProps.type = attProps.type;
const rules = attProps.rules || {};
if (typeof rules.minLength === 'number') {
fldProps.minLength = rules.minLength;
}
if (typeof rules.maxLength === 'number') {
fldProps.maxLength = rules.maxLength;
}
if (typeof rules.pattern === 'object') {
// prettier-ignore
fldProps.pattern = String(rules.pattern).replace('/^','^').replace('$/', '$');
}
if (typeof rules.format === 'format') {
fldProps.format = rules.format;
}
for (let key in rules) {
if (!['minLength', 'maxLength', 'pattern', 'format'].includes(key)) {
throw new Error(
`Schema Validation property ${key} in attribute ${fldName} of Model ${tableName} is not supported
Supported properties are 'minLength', 'maxLength'
`
);
}
}
const validations = attProps.validations || {};
if (validations.isIn && _.isArray(validations.isIn)) {
fldProps.enum = [...validations.isIn];
}
}
if (attProps.type === 'number') {
fldProps.type = attProps.type;
const rules = attProps.rules || {};
if (typeof rules.minimum === 'number') {
fldProps.minimum = rules.minimum;
}
if (typeof rules.maximum === 'number') {
fldProps.maximum = rules.maximum;
}
if (typeof rules.exclusiveMinimum === 'number') {
fldProps.exclusiveMinimum = rules.exclusiveMinimum;
}
if (typeof rules.exclusiveMaximum === 'number') {
fldProps.exclusiveMaximum = rules.exclusiveMaximum;
}
if (typeof rules.multipleOf === 'number') {
fldProps.multipleOf = rules.multipleOf;
}
for (let key in rules) {
if (
![
'minimum',
'maximum',
'exclusiveMaximum',
'exclusiveMinimum',
'multipleOf',
].includes(key)
) {
throw new Error(
`Schema Validation property ${key} in attribute ${fldName} of Model ${tableName} is not supported
Supported properties are 'minimum', 'maximum'
`
);
}
}
}
if (attProps.type === 'boolean') {
fldProps.type = attProps.type;
const rules = attProps.rules || {};
}
if (attProps.type === 'json' && _.isArray(attProps.defaultsTo)) {
fldProps.type = 'array';
const rules = attProps.rules || {};
for (let key in rules) {
if (!['items', 'uniqueItems'].includes(key)) {
throw new Error(
`Schema Validation property ${key} in attribute ${fldName} of Model ${tableName} is not supported
supported properties are 'items', 'uniqueItems'
`
);
}
}
if (rules.items && _.isPlainObject(rules.items)) {
fldProps.items = { ...rules.items };
if (fldProps.items.required && _.isArray(fldProps.items.required)) {
fldProps.items.required = [...fldProps.items.required].filter(
(r) => r !== '_key' && r !== '_id'
);
for (let r of fldProps.items.required) {
if (!fldProps.items.properties[r]) {
throw new Error(
`${r} rules property ${r} in array items of ${tableName}.${fldName} is not included in the rules properties
`
);
}
}
}
}
if (rules.items && _.isArray(rules.items)) {
fldProps.items = { ...rules.items };
}
if (rules.uniqueItems) {
fldProps.uniqueItems = true;
}
}
if (attProps.type === 'json' && _.isPlainObject(attProps.defaultsTo)) {
fldProps.type = 'object';
const rules = attProps.rules || {};
for (let key in rules) {
if (
!['properties', 'additionalProperties', 'required'].includes(key)
) {
throw new Error(
`Schema Validation property ${key} in attribute ${fldName} of Model ${tableName} is not supported
supported properties are 'properties', 'additionalProperties', 'required'
`
);
}
}
if (rules.properties && _.isPlainObject(rules.properties)) {
fldProps.properties = { ...rules.properties };
for (let p in fldProps.properties) {
const obj = fldProps.properties[p] || {};
if (
!['string', 'number', 'boolean', 'array', 'object'].includes(
obj.type
)
) {
throw new Error(`
Error setting schema validation ${p} in attribute ${fldName} of Model ${tableName}
expects object of type 'string', 'number', 'boolean', 'array', 'object'
`);
}
}
if (rules.required && _.isArray(rules.required)) {
fldProps.required = [...rules.required].filter(
(r) => r !== '_key' && r !== '_id'
);
for (let r of fldProps.required) {
if (!fldProps.properties[r]) {
throw new Error(
`${r} rules property for attribute ${fldName} in schema ${tableName} is not included in the rules properties
`
);
}
}
}
}
if (
rules.additionalProperties &&
_.isPlainObject(rules.additionalProperties)
) {
fldProps.additionalProperties = {
...rules.additionalProperties,
};
}
}
if (
attProps.type === 'json' &&
attProps.defaultsTo &&
!_.isArray(attProps.defaultsTo) &&
!_.isPlainObject(attProps.defaultsTo)
) {
throw new Error(
`Invalid defaultsTo property in attribute ${fldName} in model ${tableName}`
);
}
properties[fldName] = { ...fldProps };
}
const schema = {
rule: {
properties,
additionalProperties: Boolean(definition.additionalProperties),
...(_.isEmpty(required) ? {} : { required }),
},
level: definition.schemaValidation,
message: `
Please check your document:
Schema violation for ${tableName}
`,
};
await collection.properties({ schema: schema });
} else {
await collection.properties({ schema: null });
}
return true;
};