UNPKG

@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
// 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'