@resin/pinejs
Version:
Pine.js is a sophisticated rules-driven API engine that enables you to define rules in a structured subset of English. Those rules are used in order for Pine.js to generate a database schema and the associated [OData](http://www.odata.org/) API. This make
1,429 lines (1,338 loc) • 51.4 kB
JavaScript
// Generated by CoffeeScript 1.12.7
(function() {
var AbstractSQLCompiler, AbstractSQLCompilerVersion, BadRequestError, DEBUG, HttpError, InternalRequestError, LF2AbstractSQL, LF2AbstractSQLTranslator, LF2AbstractSQLTranslatorVersion, ODataMetadataGenerator, ParsingError, PermissionError, PermissionParsingError, PinejsClient, PinejsClientCoreFactory, Promise, SBVRParser, SbvrValidationError, SqlCompilationError, TranslationError, UnsupportedMethodError, _, abstractSqlModels, addHook, api, apiHooks, cachedCompile, checkForExpansion, cleanupModel, compileRequest, constructError, controlFlow, db, deepFreeze, devModel, env, errors, executeModel, executeModels, executeStandardModels, fetchProcessing, generateAbstractSqlModel, generateLfModel, generateModels, getAbstractSqlModel, getAndCheckBindValues, getFetchProcessingFields, getHooks, getLocalFields, handleODataRequest, instantiateHooks, isRuleAffected, memoize, memoizeWeak, memoizedCompileRule, memoizedResolvedSynonym, migrator, odataMetadata, odataNameToSqlName, odataResourceURI, permissions, prepareResponse, prettifyConstraintError, processOData, ref1, ref2, resolveNavigationResource, resolveOdataBind, resolveSynonym, respondDelete, respondGet, respondOptions, respondPost, respondPut, rollbackRequestHooks, runChangeSet, runDelete, runGet, runHooks, runPost, runPut, runQuery, runRequest, runTransaction, runURI, sbvrTypes, seModels, sqlModels, sqlNameToODataName, updateBinds, uriParser, validateModel,
slice = [].slice,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
_ = require('lodash');
Promise = require('bluebird');
Promise.config({
cancellation: true
});
cachedCompile = require('./cached-compile').cachedCompile;
LF2AbstractSQL = require('@resin/lf-to-abstract-sql');
AbstractSQLCompiler = require('@resin/abstract-sql-compiler');
AbstractSQLCompilerVersion = require('@resin/abstract-sql-compiler/package.json').version;
PinejsClientCoreFactory = require('pinejs-client-core').PinejsClientCoreFactory;
sbvrTypes = require('@resin/sbvr-types');
ref1 = require('@resin/odata-to-abstract-sql'), sqlNameToODataName = ref1.sqlNameToODataName, odataNameToSqlName = ref1.odataNameToSqlName;
deepFreeze = require('deep-freeze');
env = require('../config-loader/env');
SBVRParser = require('../extended-sbvr-parser/extended-sbvr-parser');
migrator = require('../migrator/migrator');
ODataMetadataGenerator = require('../sbvr-compiler/ODataMetadataGenerator');
devModel = require('./dev.sbvr');
permissions = require('./permissions');
uriParser = require('./uri-parser');
errors = require('./errors');
ref2 = require('./hooks'), rollbackRequestHooks = ref2.rollbackRequestHooks, instantiateHooks = ref2.instantiateHooks;
_.assign(exports, errors);
InternalRequestError = errors.InternalRequestError, ParsingError = errors.ParsingError, PermissionError = errors.PermissionError, PermissionParsingError = errors.PermissionParsingError, SbvrValidationError = errors.SbvrValidationError, SqlCompilationError = errors.SqlCompilationError, TranslationError = errors.TranslationError, UnsupportedMethodError = errors.UnsupportedMethodError, BadRequestError = errors.BadRequestError, HttpError = errors.HttpError;
controlFlow = require('./control-flow');
memoize = require('memoizee');
memoizeWeak = require('memoizee/weak');
memoizedCompileRule = memoizeWeak(function(abstractSqlQuery) {
var modifiedFields, sqlQuery;
sqlQuery = AbstractSQLCompiler.compileRule(abstractSqlQuery);
modifiedFields = AbstractSQLCompiler.getModifiedFields(abstractSqlQuery);
if (modifiedFields != null) {
deepFreeze(modifiedFields);
}
return {
sqlQuery: sqlQuery,
modifiedFields: modifiedFields
};
}, {
max: env.cache.abstractSqlCompiler.max
});
compileRequest = function(request) {
var err, modifiedFields, ref3, sqlQuery;
if (request.abstractSqlQuery != null) {
try {
ref3 = memoizedCompileRule(request.abstractSqlQuery), sqlQuery = ref3.sqlQuery, modifiedFields = ref3.modifiedFields;
request.sqlQuery = sqlQuery;
request.modifiedFields = modifiedFields;
} catch (error) {
err = error;
api[request.vocabulary].logger.error('Failed to compile abstract sql: ', request.abstractSqlQuery, err, err.stack);
throw new SqlCompilationError(err);
}
}
return request;
};
DEBUG = process.env.DEBUG;
db = null;
exports.sbvrTypes = sbvrTypes;
fetchProcessing = _.mapValues(sbvrTypes, function(arg) {
var fetchProcessing;
fetchProcessing = arg.fetchProcessing;
if (fetchProcessing != null) {
return Promise.promisify(fetchProcessing);
}
});
LF2AbstractSQLTranslator = LF2AbstractSQL.createTranslator(sbvrTypes);
LF2AbstractSQLTranslatorVersion = require('@resin/lf-to-abstract-sql/package.json').version + '+' + require('@resin/sbvr-types/package.json').version;
seModels = {};
abstractSqlModels = {};
sqlModels = {};
odataMetadata = {};
apiHooks = {
all: {},
GET: {},
PUT: {},
POST: {},
PATCH: {},
DELETE: {},
OPTIONS: {}
};
apiHooks.MERGE = apiHooks.PATCH;
memoizedResolvedSynonym = memoizeWeak(function(abstractSqlModel, resourceName) {
var sqlName;
sqlName = odataNameToSqlName(resourceName);
return _(sqlName).split('-').map(function(resourceName) {
var ref3;
return (ref3 = abstractSqlModel.synonyms[resourceName]) != null ? ref3 : resourceName;
}).join('-');
return abstractSqlModel;
}, {
primitive: true
});
exports.resolveSynonym = resolveSynonym = function(request) {
var abstractSqlModel;
abstractSqlModel = getAbstractSqlModel(request);
return memoizedResolvedSynonym(abstractSqlModel, request.resourceName);
};
exports.resolveNavigationResource = resolveNavigationResource = function(request, navigationName) {
var mapping, navigation, resolvedResourceName;
navigation = _(odataNameToSqlName(navigationName)).split('-').flatMap(function(resourceName) {
return resolveSynonym({
resourceName: resourceName,
vocabulary: request.vocabulary,
abstractSqlModel: request.abstractSqlModel
}).split('-');
}).concat('$').value();
resolvedResourceName = resolveSynonym(request);
mapping = _.get(getAbstractSqlModel(request).relationships[resolvedResourceName], navigation);
if (mapping == null) {
throw new Error("Cannot navigate from '" + request.resourceName + "' to '" + navigationName + "'");
}
if (mapping.length < 2) {
throw new Error("'" + request.resourceName + "' to '" + navigationName + "' is a field not a navigation");
}
return sqlNameToODataName(request.abstractSqlModel.tables[mapping[1][0]].name);
};
prettifyConstraintError = function(err, resourceName) {
var columns, matches, tableName;
if (err instanceof db.ConstraintError) {
if (err instanceof db.UniqueConstraintError) {
switch (db.engine) {
case 'mysql':
matches = /ER_DUP_ENTRY: Duplicate entry '.*?[^\\]' for key '(.*?[^\\])'/.exec(err);
throw new db.UniqueConstraintError('"' + sqlNameToODataName(matches[1]) + '" must be unique.');
break;
case 'postgres':
tableName = odataNameToSqlName(resourceName);
matches = new RegExp('"' + tableName + '_(.*?)_key"').exec(err);
if (matches == null) {
throw new db.UniqueConstraintError('Unique key constraint violated');
}
columns = matches[1].split('_');
throw new db.UniqueConstraintError('"' + columns.map(sqlNameToODataName).join('" and "') + '" must be unique.');
}
}
if (err instanceof db.ForeignKeyConstraintError) {
switch (db.engine) {
case 'mysql':
matches = /ER_ROW_IS_REFERENCED_: Cannot delete or update a parent row: a foreign key constraint fails \(".*?"\.(".*?").*/.exec(err);
break;
case 'postgres':
tableName = odataNameToSqlName(resourceName);
matches = new RegExp('"' + tableName + '" violates foreign key constraint ".*?" on table "(.*?)"').exec(err);
if (matches == null) {
matches = new RegExp('"' + tableName + '" violates foreign key constraint "' + tableName + '_(.*?)_fkey"').exec(err);
}
if (matches == null) {
throw new db.ForeignKeyConstraintError('Foreign key constraint violated');
}
}
throw new db.ForeignKeyConstraintError('Data is referenced by ' + sqlNameToODataName(matches[1]) + '.');
}
throw err;
}
};
exports.resolveOdataBind = resolveOdataBind = function(odataBinds, value) {
var dataType, ref3;
if (_.isObject(value) && (value.bind != null)) {
ref3 = odataBinds[value.bind], dataType = ref3[0], value = ref3[1];
}
return value;
};
getAndCheckBindValues = function(vocab, odataBinds, bindings, values) {
var sqlModelTables;
sqlModelTables = sqlModels[vocab].tables;
return Promise.map(bindings, function(binding) {
var dataType, field, fieldName, ref3, ref4, ref5, referencedName, sqlFieldName, sqlTableName, tableName, value;
if (binding[0] === 'Bind') {
if (_.isArray(binding[1])) {
ref3 = binding[1], tableName = ref3[0], fieldName = ref3[1];
referencedName = tableName + '.' + fieldName;
value = values[referencedName];
if (value === void 0) {
value = values[fieldName];
}
value = resolveOdataBind(odataBinds, value);
sqlTableName = odataNameToSqlName(tableName);
sqlFieldName = odataNameToSqlName(fieldName);
field = _.find(sqlModelTables[sqlTableName].fields, {
fieldName: sqlFieldName
});
} else if (_.isInteger(binding[1])) {
if (binding[1] >= odataBinds.length) {
console.error("Invalid binding '" + binding[1] + "' for binds: ", odataBinds);
throw new Error('Invalid binding');
}
ref4 = odataBinds[binding[1]], dataType = ref4[0], value = ref4[1];
field = {
dataType: dataType
};
} else if (_.isString(binding[1])) {
if (!odataBinds.hasOwnProperty(binding[1])) {
console.error("Invalid binding '" + binding[1] + "' for binds: ", odataBinds);
throw new Error('Invalid binding');
}
ref5 = odataBinds[binding[1]], dataType = ref5[0], value = ref5[1];
field = {
dataType: dataType
};
} else {
throw new Error("Unknown binding: " + binding);
}
} else {
dataType = binding[0], value = binding[1];
field = {
dataType: dataType
};
}
if (value === void 0) {
throw new Error("Bind value cannot be undefined: " + binding);
}
return AbstractSQLCompiler.dataTypeValidate(value, field).tapCatch(function(e) {
return e.message = '"' + fieldName + '" ' + e.message;
});
});
};
isRuleAffected = (function() {
var checkModifiedFields;
checkModifiedFields = function(referencedFields, modifiedFields) {
var refs;
refs = referencedFields[modifiedFields.table];
if (refs == null) {
return false;
}
if (modifiedFields.fields == null) {
return true;
}
return _.intersection(refs, modifiedFields.fields).length > 0;
};
return function(rule, request) {
var modifiedFields;
if ((request != null ? request.abstractSqlQuery : void 0) == null) {
return false;
}
if (rule.referencedFields == null) {
return true;
}
modifiedFields = request.modifiedFields;
if (modifiedFields == null) {
console.warn("Could not determine the modified table/fields info for '" + request.method + "' to " + request.vocabulary, request.abstractSqlQuery);
return true;
}
if (_.isArray(modifiedFields)) {
return _.any(modifiedFields, _.partial(checkModifiedFields, rule.referencedFields));
}
return checkModifiedFields(rule.referencedFields, modifiedFields);
};
})();
exports.validateModel = validateModel = function(tx, modelName, request) {
return Promise.map(sqlModels[modelName].rules, function(rule) {
if (!isRuleAffected(rule, request)) {
return;
}
return getAndCheckBindValues(modelName, null, rule.bindings, null).then(function(values) {
return tx.executeSql(rule.sql, values);
}).then(function(result) {
var ref3;
if ((ref3 = result.rows[0].result) === false || ref3 === 0 || ref3 === '0') {
throw new SbvrValidationError(rule.structuredEnglish);
}
});
});
};
exports.generateLfModel = generateLfModel = function(seModel) {
return cachedCompile('lfModel', SBVRParser.version, seModel, function() {
return SBVRParser.matchAll(seModel, 'Process');
});
};
exports.generateAbstractSqlModel = generateAbstractSqlModel = function(lfModel) {
return cachedCompile('abstractSqlModel', LF2AbstractSQLTranslatorVersion, lfModel, function() {
return LF2AbstractSQLTranslator(lfModel, 'Process');
});
};
generateModels = function(vocab, seModel) {
var abstractSqlModel, e, lfModel, metadata, sqlModel;
try {
lfModel = generateLfModel(seModel);
} catch (error) {
e = error;
console.error("Error parsing model '" + vocab + "':", e);
throw new Error(("Error parsing model '" + vocab + "': ") + e);
}
try {
abstractSqlModel = generateAbstractSqlModel(lfModel);
} catch (error) {
e = error;
console.error("Error translating model '" + vocab + "':", e);
throw new Error(("Error translating model '" + vocab + "': ") + e);
}
try {
sqlModel = cachedCompile('sqlModel', AbstractSQLCompilerVersion + '+' + db.engine, abstractSqlModel, function() {
return AbstractSQLCompiler.compileSchema(abstractSqlModel);
});
metadata = cachedCompile('metadata', ODataMetadataGenerator.version, {
vocab: vocab,
sqlModel: sqlModel
}, function() {
return ODataMetadataGenerator(vocab, sqlModel);
});
} catch (error) {
e = error;
console.error("Error compiling model '" + vocab + "':", e);
throw new Error(("Error compiling model '" + vocab + "': ") + e);
}
return {
lfModel: lfModel,
abstractSqlModel: abstractSqlModel,
sqlModel: sqlModel,
metadata: metadata
};
};
exports.executeModel = executeModel = function(tx, model, callback) {
return executeModels(tx, [model], callback);
};
exports.executeModels = executeModels = function(tx, models, callback) {
return Promise.map(models, function(model) {
var seModel, vocab;
seModel = model.modelText;
vocab = model.apiRoot;
return migrator.run(tx, model).then(function() {
var abstractSqlModel, lfModel, metadata, ref3, sqlModel;
ref3 = generateModels(vocab, seModel), lfModel = ref3.lfModel, abstractSqlModel = ref3.abstractSqlModel, sqlModel = ref3.sqlModel, metadata = ref3.metadata;
return Promise.each(sqlModel.createSchema, function(createStatement) {
var promise;
promise = tx.executeSql(createStatement);
if (db.engine === 'websql') {
promise["catch"](function(err) {
return console.warn("Ignoring errors in the create table statements for websql as it doesn't support CREATE IF NOT EXISTS", err);
});
}
return promise;
}).then(function() {
seModels[vocab] = seModel;
_.each(abstractSqlModel.tables, function(table) {
getLocalFields(table);
return getFetchProcessingFields(table);
});
deepFreeze(abstractSqlModel);
abstractSqlModels[vocab] = abstractSqlModel;
sqlModels[vocab] = sqlModel;
odataMetadata[vocab] = metadata;
return validateModel(tx, vocab);
}).then(function() {
var key, ref4, ref5, ref6, ref7, value;
api[vocab] = new PinejsClient('/' + vocab + '/');
api[vocab].logger = {};
for (key in console) {
value = console[key];
if (_.isFunction(value)) {
if ((ref4 = (ref5 = (ref6 = model.logging) != null ? ref6[key] : void 0) != null ? ref5 : (ref7 = model.logging) != null ? ref7["default"] : void 0) != null ? ref4 : true) {
api[vocab].logger[key] = (function(key) {
return function() {
return console[key].apply(console, [vocab + ':'].concat(slice.call(arguments)));
};
})(key);
} else {
api[vocab].logger[key] = _.noop;
}
} else {
api[vocab].logger[key] = value;
}
}
return {
vocab: vocab,
se: seModel,
lf: lfModel,
abstractsql: abstractSqlModel,
sql: sqlModel
};
});
});
}).map(function(model) {
var updateModel;
updateModel = function(modelType, modelText) {
return api.dev.get({
resource: 'model',
passthrough: {
tx: tx,
req: permissions.rootRead
},
options: {
$select: 'id',
$filter: {
is_of__vocabulary: model.vocab,
model_type: modelType
}
}
}).then(function(result) {
var body, id, method, ref3, uri;
method = 'POST';
uri = '/dev/model';
body = {
is_of__vocabulary: model.vocab,
model_value: modelText,
model_type: modelType
};
id = (ref3 = result[0]) != null ? ref3.id : void 0;
if (id != null) {
uri += '(' + id + ')';
method = 'PATCH';
body.id = id;
} else {
uri += '?returnResource=false';
}
return runURI(method, uri, body, tx, permissions.root);
});
};
return Promise.all([updateModel('se', model.se), updateModel('lf', model.lf), updateModel('abstractsql', model.abstractsql), updateModel('sql', model.sql)]);
}).tapCatch(function() {
Promise.map(models, function(model) {
return cleanupModel(model.apiRoot);
});
}).nodeify(callback);
};
cleanupModel = function(vocab) {
delete seModels[vocab];
delete abstractSqlModels[vocab];
delete sqlModels[vocab];
delete odataMetadata[vocab];
return delete api[vocab];
};
getHooks = (function() {
var _getHooks, getMethodHooks, getResourceHooks, getVocabHooks, mergeHooks;
mergeHooks = function(a, b) {
return _.mergeWith({}, a, b, function(a, b) {
if (_.isArray(a)) {
return a.concat(b);
}
});
};
getResourceHooks = function(vocabHooks, resourceName) {
if (vocabHooks == null) {
return {};
}
if (resourceName == null) {
return vocabHooks['all'];
}
return mergeHooks(vocabHooks[resourceName], vocabHooks['all']);
};
getVocabHooks = function(methodHooks, vocabulary, resourceName) {
if (methodHooks == null) {
return {};
}
return mergeHooks(getResourceHooks(methodHooks[vocabulary], resourceName), getResourceHooks(methodHooks['all'], resourceName));
};
getMethodHooks = memoize(function(method, vocabulary, resourceName) {
return mergeHooks(getVocabHooks(apiHooks[method], vocabulary, resourceName), getVocabHooks(apiHooks['all'], vocabulary, resourceName));
}, {
primitive: true
});
_getHooks = function(request) {
var resourceName;
if (request.resourceName != null) {
resourceName = resolveSynonym(request);
}
return instantiateHooks(getMethodHooks(request.method, request.vocabulary, resourceName));
};
_getHooks.clear = function() {
return getMethodHooks.clear();
};
return _getHooks;
})();
runHooks = Promise.method(function(hookName, args) {
var hooks, ref3, ref4, requestHooks;
hooks = args.req.hooks[hookName] || [];
requestHooks = (ref3 = args.request) != null ? (ref4 = ref3.hooks) != null ? ref4[hookName] : void 0 : void 0;
if (requestHooks != null) {
hooks = hooks.concat(requestHooks);
}
if (hooks.length === 0) {
return;
}
Object.defineProperty(args, 'api', {
get: _.once(function() {
return api[args.request.vocabulary].clone({
passthrough: _.pick(args, 'req', 'tx')
});
})
});
return Promise.map(hooks, function(hook) {
return hook.run(args);
});
});
exports.deleteModel = function(vocabulary, callback) {
return db.transaction(function(tx) {
var dropStatements, ref3;
dropStatements = _.map((ref3 = sqlModels[vocabulary]) != null ? ref3.dropSchema : void 0, function(dropStatement) {
return tx.executeSql(dropStatement);
});
return Promise.all(dropStatements.concat([
api.dev["delete"]({
resource: 'model',
passthrough: {
tx: tx,
req: permissions.root
},
options: {
$filter: {
is_of__vocabulary: vocabulary
}
}
})
]));
}).then(function() {
return cleanupModel(vocabulary);
}).nodeify(callback);
};
exports.getID = function(vocab, request) {
var comparison, i, idField, j, len, len1, ref3, ref4, whereClause;
idField = sqlModels[vocab].tables[request.resourceName].idField;
ref3 = request.abstractSqlQuery;
for (i = 0, len = ref3.length; i < len; i++) {
whereClause = ref3[i];
if (whereClause[0] === 'Where') {
ref4 = whereClause.slice(1);
for (j = 0, len1 = ref4.length; j < len1; j++) {
comparison = ref4[j];
if (!(comparison[0] === 'Equals')) {
continue;
}
if (comparison[1][2] === idField) {
return comparison[2][1];
}
if (comparison[2][2] === idField) {
return comparison[1][1];
}
}
}
}
return 0;
};
checkForExpansion = (function() {
return Promise.method(function(vocab, abstractSqlModel, parentResourceName, fieldName, instance) {
var field, mappingResourceName;
try {
field = JSON.parse(instance[fieldName]);
} catch (error) {
field = instance[fieldName];
}
if (_.isArray(field)) {
mappingResourceName = resolveNavigationResource({
abstractSqlModel: abstractSqlModel,
vocabulary: vocab,
resourceName: parentResourceName
}, fieldName);
return processOData(vocab, abstractSqlModel, mappingResourceName, field).then(function(expandedField) {
instance[fieldName] = expandedField;
});
} else if (field != null) {
mappingResourceName = resolveNavigationResource({
abstractSqlModel: abstractSqlModel,
vocabulary: vocab,
resourceName: parentResourceName
}, fieldName);
instance[fieldName] = {
__deferred: {
uri: '/' + vocab + '/' + mappingResourceName + '(' + field + ')'
},
__id: field
};
}
});
})();
odataResourceURI = function(vocab, resourceName, id) {
id = _.isString(id) ? "'" + encodeURIComponent(id) + "'" : id;
return '/' + vocab + '/' + resourceName + '(' + id + ')';
};
getLocalFields = function(table) {
var dataType, fieldName, i, len, odataName, ref3, ref4;
if (table.localFields == null) {
table.localFields = {};
ref3 = table.fields;
for (i = 0, len = ref3.length; i < len; i++) {
ref4 = ref3[i], fieldName = ref4.fieldName, dataType = ref4.dataType;
if (!(dataType !== 'ForeignKey')) {
continue;
}
odataName = sqlNameToODataName(fieldName);
table.localFields[odataName] = true;
}
}
return table.localFields;
};
getFetchProcessingFields = function(table) {
return table.fetchProcessingFields != null ? table.fetchProcessingFields : table.fetchProcessingFields = _(table.fields).filter(function(arg) {
var dataType;
dataType = arg.dataType;
return fetchProcessing[dataType] != null;
}).map(function(arg) {
var dataType, fieldName, odataName;
fieldName = arg.fieldName, dataType = arg.dataType;
odataName = sqlNameToODataName(fieldName);
return [odataName, fetchProcessing[dataType]];
}).fromPairs().value();
};
processOData = function(vocab, abstractSqlModel, resourceName, rows) {
var count, expandableFields, fetchProcessingFields, instances, instancesPromise, localFields, odataIdField, processedFields, sqlResourceName, table;
if (rows.length === 0) {
return Promise.fulfilled([]);
}
if (rows.length === 1) {
if (rows[0].$count != null) {
count = parseInt(rows[0].$count, 10);
return Promise.fulfilled(count);
}
}
sqlResourceName = resolveSynonym({
abstractSqlModel: abstractSqlModel,
vocabulary: vocab,
resourceName: resourceName
});
table = abstractSqlModel.tables[sqlResourceName];
odataIdField = sqlNameToODataName(table.idField);
instances = rows.map(function(instance) {
instance.__metadata = {
uri: odataResourceURI(vocab, resourceName, +instance[odataIdField]),
type: ''
};
return instance;
});
instancesPromise = Promise.fulfilled();
localFields = getLocalFields(table);
expandableFields = _.filter(_.keys(instances[0]), function(fieldName) {
return fieldName.slice(0, 2) !== '__' && !localFields.hasOwnProperty(fieldName);
});
if (expandableFields.length > 0) {
instancesPromise = Promise.map(instances, function(instance) {
return Promise.map(expandableFields, function(fieldName) {
return checkForExpansion(vocab, abstractSqlModel, sqlResourceName, fieldName, instance);
});
});
}
fetchProcessingFields = getFetchProcessingFields(table);
processedFields = _.filter(_.keys(instances[0]), function(fieldName) {
return fieldName.slice(0, 2) !== '__' && fetchProcessingFields.hasOwnProperty(fieldName);
});
if (processedFields.length > 0) {
instancesPromise = instancesPromise.then(function() {
return Promise.map(instances, function(instance) {
return Promise.map(processedFields, function(resourceName) {
return fetchProcessingFields[resourceName](instance[resourceName]).then(function(result) {
instance[resourceName] = result;
});
});
});
});
}
return instancesPromise.then(function() {
return instances;
});
};
exports.runRule = (function() {
var LF2AbstractSQLPrepHack, translator;
LF2AbstractSQLPrepHack = LF2AbstractSQL.LF2AbstractSQLPrep._extend({
CardinalityOptimisation: function() {
return this._pred(false);
}
});
translator = LF2AbstractSQL.LF2AbstractSQL.createInstance();
translator.addTypes(sbvrTypes);
return function(vocab, rule, callback) {
return Promise["try"](function() {
var abstractSqlModel, e, fetchingViolators, formulationType, lfModel, logger, resourceName, ruleAbs, ruleBody, ruleLF, seModel, slfModel, wantNonViolators;
seModel = seModels[vocab];
logger = api[vocab].logger;
try {
lfModel = SBVRParser.matchAll(seModel + '\nRule: ' + rule, 'Process');
} catch (error) {
e = error;
logger.error('Error parsing rule', rule, e, e.stack);
throw new Error(['Error parsing rule', rule, e]);
}
ruleLF = lfModel[lfModel.length - 1];
lfModel = lfModel.slice(0, -1);
try {
slfModel = LF2AbstractSQL.LF2AbstractSQLPrep.match(lfModel, 'Process');
slfModel.push(ruleLF);
slfModel = LF2AbstractSQLPrepHack.match(slfModel, 'Process');
translator.reset();
abstractSqlModel = translator.match(slfModel, 'Process');
} catch (error) {
e = error;
logger.error('Error compiling rule', rule, e, e.stack);
throw new Error(['Error compiling rule', rule, e]);
}
formulationType = ruleLF[1][0];
resourceName = ruleLF[1][1][0] === 'LogicalNegation' ? ruleLF[1][1][1][1][2][1] : ruleLF[1][1][1][2][1];
fetchingViolators = false;
ruleAbs = abstractSqlModel.rules.slice(-1)[0];
ruleBody = _.find(ruleAbs, {
0: 'Body'
});
if (ruleBody[1][0] === 'Not' && ruleBody[1][1][0] === 'Exists' && ruleBody[1][1][1][0] === 'SelectQuery') {
ruleBody[1] = ruleBody[1][1][1];
fetchingViolators = true;
} else if (ruleBody[1][0] === 'Exists' && ruleBody[1][1][0] === 'SelectQuery') {
ruleBody[1] = ruleBody[1][1];
} else {
throw new Error('Unsupported rule formulation');
}
wantNonViolators = formulationType === 'PossibilityFormulation' || formulationType === 'PermissibilityFormulation';
if (wantNonViolators === fetchingViolators) {
ruleBody[1] = _.map(ruleBody[1], function(queryPart) {
if (queryPart[0] !== 'Where') {
return queryPart;
}
if (queryPart.length > 2) {
throw new Error('Unsupported rule formulation');
}
return ['Where', ['Not', queryPart[1]]];
});
}
ruleBody[1] = _.map(ruleBody[1], function(queryPart) {
if (queryPart[0] !== 'Select') {
return queryPart;
}
return ['Select', '*'];
});
rule = AbstractSQLCompiler.compileRule(ruleBody);
return getAndCheckBindValues(vocab, null, rule.bindings, null).then(function(values) {
return db.executeSql(rule.query, values);
}).then(function(result) {
var filter, ids, odataIdField, table;
table = abstractSqlModels[vocab].tables[resourceName];
odataIdField = sqlNameToODataName(table.idField);
ids = result.rows.map(function(row) {
return row[table.idField];
});
ids = _.uniq(ids);
ids = _.map(ids, function(id) {
return odataIdField + ' eq ' + id;
});
filter = ids.length > 0 ? ids.join(' or ') : '0 eq 1';
return runURI('GET', '/' + vocab + '/' + sqlNameToODataName(table.resourceName) + '?$filter=' + filter, null, null, permissions.rootRead).then(function(result) {
result.__formulationType = formulationType;
result.__resourceName = resourceName;
return result;
});
});
}).nodeify(callback);
};
})();
exports.PinejsClient = PinejsClient = (function(superClass) {
extend(PinejsClient, superClass);
function PinejsClient() {
return PinejsClient.__super__.constructor.apply(this, arguments);
}
PinejsClient.prototype._request = function(arg) {
var body, custom, method, req, tx, url;
method = arg.method, url = arg.url, body = arg.body, tx = arg.tx, req = arg.req, custom = arg.custom;
return runURI(method, url, body, tx, req, custom);
};
return PinejsClient;
})(PinejsClientCoreFactory(Promise));
exports.api = api = {};
exports.runURI = runURI = function(method, uri, body, tx, req, custom, callback) {
var apiKey, message, user;
if (body == null) {
body = {};
}
if ((callback != null) && !_.isFunction(callback)) {
message = 'Called runURI with a non-function callback?!';
console.trace(message);
return Promise.rejected(message);
}
if (_.isObject(req)) {
user = req.user;
apiKey = req.apiKey;
} else {
if (req != null) {
console.warn('Non-object req passed to runURI?', req, new Error().stack);
}
user = {
permissions: []
};
}
_.each(body, function(v, k) {
if (v === void 0) {
return delete body[k];
}
});
req = {
on: _.noop,
custom: custom,
user: user,
apiKey: apiKey,
method: method,
url: uri,
body: body,
params: {},
query: {},
tx: tx
};
return new Promise(function(resolve, reject) {
var next, res;
res = {
on: _.noop,
statusCode: 200,
status: function(statusCode1) {
this.statusCode = statusCode1;
return this;
},
sendStatus: function(statusCode) {
if (statusCode >= 400) {
return reject(statusCode);
} else {
return resolve();
}
},
send: function(statusCode) {
if (statusCode == null) {
statusCode = this.statusCode;
}
return this.sendStatus(statusCode);
},
json: function(data, statusCode) {
if (statusCode == null) {
statusCode = this.statusCode;
}
if (statusCode >= 400) {
return reject(data);
} else {
return resolve(data);
}
},
set: _.noop,
type: _.noop
};
next = function(route) {
console.warn('Next called on a runURI?!', method, uri, route);
return res.sendStatus(500);
};
return handleODataRequest(req, res, next);
}).nodeify(callback);
};
exports.getAbstractSqlModel = getAbstractSqlModel = function(request) {
if (request.abstractSqlModel == null) {
request.abstractSqlModel = abstractSqlModels[request.vocabulary];
}
return request.abstractSqlModel;
};
exports.getAffectedIds = Promise.method(function(arg) {
var req, request, tx;
req = arg.req, request = arg.request, tx = arg.tx;
if (request.method === 'GET') {
throw new Error('Cannot call `getAffectedIds` with a GET request');
}
return uriParser.parseOData({
method: request.method,
url: "/" + request.vocabulary + request.url
}).then(function(request) {
var abstractSqlModel, idField, resourceName, resourceTable;
abstractSqlModel = getAbstractSqlModel(request);
resourceName = resolveSynonym(request);
resourceTable = abstractSqlModel.tables[resourceName];
if (resourceTable == null) {
throw new Error('Unknown resource: ' + request.resourceName);
}
idField = resourceTable.idField;
_.set(request.odataQuery, ['options', '$select'], {
properties: [
{
name: idField
}
]
});
delete request.odataQuery.options.$expand;
return permissions.addPermissions(req, request).then(function() {
var doRunQuery;
request.method = 'GET';
request = uriParser.translateUri(request);
request = compileRequest(request);
doRunQuery = function(tx) {
return runQuery(tx, request).then(function(result) {
return _.map(result.rows, idField);
});
};
if (tx != null) {
return doRunQuery(tx);
} else {
return runTransaction(req, doRunQuery);
}
});
});
});
exports.handleODataRequest = handleODataRequest = function(req, res, next) {
var apiRoot, handlePromise, mapSeries, ref3, reqHooks, url;
url = req.url.split('/');
apiRoot = url[1];
if ((apiRoot == null) || (abstractSqlModels[apiRoot] == null)) {
return next('route');
}
if (DEBUG) {
api[apiRoot].logger.log('Parsing', req.method, req.url);
}
mapSeries = controlFlow.getMappingFn(req.headers);
req.hooks = reqHooks = getHooks({
method: req.method,
vocabulary: apiRoot
});
req.on('close', function() {
handlePromise.cancel();
return rollbackRequestHooks(reqHooks);
});
res.on('close', function() {
handlePromise.cancel();
return rollbackRequestHooks(reqHooks);
});
if ((ref3 = req.tx) != null) {
ref3.on('rollback', function() {
return rollbackRequestHooks(reqHooks);
});
}
return handlePromise = runHooks('PREPARSE', {
req: req,
tx: req.tx
}).then(function() {
var body, method, ref4;
method = req.method, url = req.url, body = req.body;
body = ((ref4 = req.batch) != null ? ref4.length : void 0) > 0 ? req.batch : [
{
method: method,
url: url,
data: body
}
];
return mapSeries(body, function(bodypart) {
return uriParser.parseOData(bodypart).then(controlFlow.liftP(function(request) {
req.hooks = {};
request.hooks = getHooks(request);
return runHooks('POSTPARSE', {
req: req,
request: request,
tx: req.tx
})["return"](request).then(uriParser.translateUri).then(compileRequest).tapCatch(function() {
rollbackRequestHooks(reqHooks);
return rollbackRequestHooks(request);
});
})).then(function(request) {
return runTransaction(req, function(tx) {
tx.on('rollback', function() {
rollbackRequestHooks(reqHooks);
return rollbackRequestHooks(request);
});
if (_.isArray(request)) {
env = new Map();
return Promise.reduce(request, runChangeSet(req, res, tx), env).then(function(env) {
return Array.from(env.values());
});
} else {
return runRequest(req, res, tx, request);
}
});
});
});
}).then(function(results) {
return mapSeries(results, function(result) {
if (_.isError(result)) {
return constructError(result);
} else {
return result;
}
});
}).then(function(responses) {
var body, headers, ref4, ref5, status;
res.set('Cache-Control', 'no-cache');
if (!(((ref4 = req.batch) != null ? ref4.length : void 0) > 0)) {
ref5 = responses[0], body = ref5.body, headers = ref5.headers, status = ref5.status;
_.forEach(headers, function(headerValue, headerName) {
return res.set(headerName, headerValue);
});
if (!body) {
if (status != null) {
return res.sendStatus(status);
} else {
console.error('No status or body set', req.url, responses);
return res.sendStatus(500);
}
} else {
if (status != null) {
res.status(status);
}
return res.json(body);
}
} else {
return res.status(200).sendMulti(responses);
}
})["catch"](function(e) {
console.error('An error occured while constructing the response', e, e.stack);
return res.sendStatus(500);
});
};
constructError = function(e) {
return Promise.reject(e)["catch"](SbvrValidationError, function(err) {
return {
status: 400,
body: err.message
};
})["catch"](PermissionError, function(err) {
return {
status: 401,
body: err.message
};
})["catch"](SqlCompilationError, TranslationError, ParsingError, PermissionParsingError, function(err) {
return {
status: 500
};
})["catch"](UnsupportedMethodError, function(err) {
return {
status: 405,
body: err.message
};
})["catch"](HttpError, function(err) {
return {
status: err.status,
body: err.getResponseBody()
};
})["catch"](e, function(err) {
console.error(err);
if (_.isError(err)) {
err = err.message;
}
return {
status: 404,
body: err
};
});
};
runRequest = function(req, res, tx, request) {
var logger;
logger = api[request.vocabulary].logger;
if (DEBUG) {
logger.log('Running', req.method, req.url);
}
return runHooks('PRERUN', {
req: req,
request: request,
tx: tx
}).then(function() {
switch (request.method) {
case 'GET':
return runGet(req, res, request, tx);
case 'POST':
return runPost(req, res, request, tx);
case 'PUT':
case 'PATCH':
case 'MERGE':
return runPut(req, res, request, tx);
case 'DELETE':
return runDelete(req, res, request, tx);
}
})["catch"](db.DatabaseError, function(err) {
prettifyConstraintError(err, request.resourceName);
logger.error(err, err.stack);
throw err;
})["catch"](EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, function(err) {
logger.error(err, err.stack);
throw new InternalRequestError();
}).tap(function(result) {
return runHooks('POSTRUN', {
req: req,
request: request,
result: result,
tx: tx
});
}).then(function(result) {
return prepareResponse(req, res, request, result, tx);
});
};
runChangeSet = function(req, res, tx) {
return function(env, request) {
request = updateBinds(env, request);
return runRequest(req, res, tx, request).then(function(result) {
result.headers['Content-Id'] = request.id;
env.set(request.id, result);
return env;
});
};
};
updateBinds = function(env, request) {
if (request._defer) {
request.odataBinds = _.map(request.odataBinds, function(arg) {
var id, ref, ref3, tag;
tag = arg[0], id = arg[1];
if (tag === 'ContentReference') {
ref = env.get(id);
if (_.isUndefined(ref != null ? (ref3 = ref.body) != null ? ref3.id : void 0 : void 0)) {
throw BadRequestError('Reference to a non existing resource in Changeset');
} else {
return uriParser.parseId(ref.body.id);
}
} else {
return [tag, id];
}
});
}
return request;
};
prepareResponse = function(req, res, request, result, tx) {
return Promise["try"](function() {
switch (request.method) {
case 'GET':
return respondGet(req, res, request, result, tx);
case 'POST':
return respondPost(req, res, request, result, tx);
case 'PUT':
case 'PATCH':
case 'MERGE':
return respondPut(req, res, request, result, tx);
case 'DELETE':
return respondDelete(req, res, request, result, tx);
case 'OPTIONS':
return respondOptions(req, res, request, result, tx);
default:
throw new UnsupportedMethodError();
}
});
};
runTransaction = function(req, callback) {
if (req.tx != null) {
return callback(req.tx);
} else {
return db.transaction(callback);
}
};
runQuery = function(tx, request, queryIndex, addReturning) {
var odataBinds, sqlQuery, values, vocabulary;
values = request.values, odataBinds = request.odataBinds, sqlQuery = request.sqlQuery, vocabulary = request.vocabulary;
if (queryIndex != null) {
sqlQuery = sqlQuery[queryIndex];
}
return getAndCheckBindValues(vocabulary, odataBinds, sqlQuery.bindings, values).then(function(values) {
if (DEBUG) {
api[vocabulary].logger.log(sqlQuery.query, values);
}
sqlQuery.values = values;
return tx.executeSql(sqlQuery.query, values, addReturning);
});
};
runGet = function(req, res, request, tx) {
if (request.sqlQuery != null) {
return runQuery(tx, request);
}
};
respondGet = function(req, res, request, result, tx) {
var vocab;
vocab = request.vocabulary;
if (request.sqlQuery != null) {
return processOData(vocab, getAbstractSqlModel(request), request.resourceName, result.rows).then(function(d) {
return runHooks('PRERESPOND', {
req: req,
res: res,
request: request,
result: result,
data: d,
tx: tx
}).then(function() {
return {
body: {
d: d
},
headers: {
contentType: 'application/json'
}
};
});
});
} else {
if (request.resourceName === '$metadata') {
return {
body: odataMetadata[vocab],
headers: {
contentType: 'xml'
}
};
} else {
return {
status: 404
};
}
}
};
runPost = function(req, res, request, tx) {
var idField, vocab;
vocab = request.vocabulary;
idField = getAbstractSqlModel(request).tables[resolveSynonym(request)].idField;
return runQuery(tx, request, null, idField).tap(function(sqlResult) {
if (sqlResult.rowsAffected === 0) {
throw new PermissionError();
}
return validateModel(tx, vocab, request);
}).then(function(sqlResult) {
if (request.abstractSqlQuery[0] === 'UpdateQuery') {
return request.sqlQuery.values[0];
} else {
return sqlResult.insertId;
}
});
};
respondPost = function(req, res, request, result, tx) {
var id, location, vocab;
vocab = request.vocabulary;
id = result;
location = odataResourceURI(vocab, request.resourceName, id);
api[vocab].logger.log('Insert ID: ', request.resourceName, id);
return Promise["try"](function() {
var onlyId, ref3;
onlyId = {
d: [
{
id: id
}
]
};
if ((ref3 = _.get(request, ['odataQuery', 'options', 'returnResource'])) === '0' || ref3 === 'false') {
return onlyId;
}
return runURI('GET', location, null, tx, req).catchReturn(onlyId);
}).then(function(result) {
return runHooks('PRERESPOND', {
req: req,
res: res,
request: request,
result: result,
tx: tx
}).then(function() {
return {
status: 201,
body: result.d[0],
headers: {
contentType: 'application/json',
Location: location
}
};
});
});
};
runPut = function(req, res, request, tx) {
var vocab;
vocab = request.vocabulary;
return Promise["try"](function() {
if (_.isArray(request.sqlQuery)) {
return runQuery(tx, request, 1).then(function(result) {
if (result.rowsAffected === 0) {
return runQuery(tx, request, 0);
}
});
} else {
return runQuery(tx, request);
}
}).then(function() {
return validateModel(tx, vocab, request);
});
};
respondPut = respondDelete = respondOptions = function(req, res, request, result, tx) {
return runHooks('PRERESPOND', {
req: req,
res: res,
request: request,
tx: tx
}).then(function() {
return {
status: 200,
headers: {}
};
});
};
runDelete = function(req, res, request, tx) {
var vocab;
vocab = request.vocabulary;
return runQuery(tx, request).then(function() {
return validateModel(tx, vocab, request);
});
};
exports.executeStandardModels = executeStandardModels = function(tx, callback) {
return executeModel(tx, {
apiRoot: 'dev',
modelText: devModel,
logging: {
log: false
}
}).then(function() {
return executeModels(tx, permissions.config.models);
}).then(function() {
return console.info('Sucessfully executed standard models.');
}).tapCatch(function(err) {
return console.error('Failed to execute standard models.', err, err.stack);
}).nodeify(callback);
};
exports.addSideEffectHook = function(method, apiRoot, resourceName, hooks) {
var sideEffectHook;
sideEffectHook = _.mapValues(hooks, function(hook) {
return {
HOOK: hook,
effects: true
};
});
return addHook(method, apiRoot, resourceName, sideEffectHook);
};
exports.addPureHook = function(method, apiRoot, resourceName, hooks) {
var pureHooks;
pureHooks = _.mapValues(hooks, function(hook) {
return {
HOOK: hook,
effects: false
};
});
return addHook(method, apiRoot, resourceName, pureHooks);
};
addHook = function(method, apiRoot, resourceName, hooks) {
var apiRootHooks, hook, hookType, methodHooks, origResourceName, resourceHooks;
methodHooks = apiHooks[method];
if (methodHooks == null) {
throw new Error('Unsupported method: ' + method);
}
if (apiRoot !== 'all' && (abstractSqlModels[apiRoot] == null)) {
throw new Error('Unknown api root: ' + apiRoot);
}
if (resourceName !== 'all') {
origResourceName = resourceName;
resourceName = resolveSynonym({
vocabulary: apiRoot,
resourceName: resourceName
});
if (abstractSqlModels[apiRoot].tables[resourceName] == null) {
throw new Error('Unknown resource for api root: ' + origResourceName + ', ' + apiRoot);
}
}
for (hookType in hooks) {
hook = hooks[hookType];
if (hookType !== 'PREPARSE'