UNPKG

@mean-expert/loopback-sdk-builder

Version:

Tool for auto-generating Software Development Kits (SDKs) for LoopBack

214 lines (193 loc) 7 kB
/** * Looks through each value in the object, * returning an object of all the values that pass a truth test (predicate). * @param obj * @param predicate * @return {{}} */ let objectFilter = (obj, predicate) => { let result = {}; for (let key in obj) { if (obj.hasOwnProperty(key) && predicate(obj[key], key) ) { result[key] = obj[key]; } } return result; }; /** * @author Miroslav Bajtos <miroslav@strongloop.com> * @license MIT and IBM StrongLoop License * @description * Based on the original services.js discovery file from the loopback-sdk-angular * It has been updated to fit the loopback-sdk-builder needs. * * https://github.com/strongloop/loopback-sdk-angular/blob/master/lib/services.js **/ exports.describeModels = function describeModels(app) { var models = {}; app.handler('rest').adapter.getClasses().forEach(function (c) { var name = capitalize(c.name); c.description = c.sharedClass.ctor.settings.description; // Tell SDK Builder to generate model by default, unless user c.sharedClass.ctor.settings = Object.assign( {}, c.sharedClass.ctor.settings ); c.sharedClass.ctor.settings.sdk = Object.assign( {enabled: true}, c.sharedClass.ctor.settings.sdk ); // Tell SDK to blacklist specific methods c.sharedClass.ctor.settings.sdk.blacklist = Object.assign( {}, // Will we want to add methods to blacklist by default??? c.sharedClass.ctor.settings.sdk.blacklist || {} ); if (!c.ctor) { // Skip classes that don't have a shared ctor // as they are not LoopBack models console.error('Skipping %j as it is not a LoopBack model', name); return; } // The URL of prototype methods include sharedCtor parameters like ":id" // Because all $resource methods are static (non-prototype) in ngResource, // the sharedCtor parameters should be added to the parameters // of prototype methods. c.methods.forEach(function fixArgsOfPrototypeMethods(method, key) { // Fix for REST DataSource with invalid param configuration if (method.name === 'invoke' && method.sharedMethod.isStatic) { method.accepts.forEach((param) => { if (!param.arg && param.name) { param.arg = param.name; param.http = { source: 'body' }; } }); } // Add CreateMany Support if (method.name.match(/(^createMany)/)) return if (method.name.match(/(^create$|__create__)/)) { var createMany = Object.create(method); createMany.name = method.name.replace(/create/g, 'createMany'); createMany.isReturningArray = function () { return true; }; c.methods.push(createMany); } var ctor = method.restClass.ctor; if (!ctor || method.sharedMethod.isStatic) return; method.accepts = ctor.accepts.concat(method.accepts); var loaded = {}; // Clean duplicated params from diff sources if (method.name.match(/createMany/)) { method.accepts.forEach((param, index, arr) => { if (loaded[param.arg]) { arr.splice(index, 1); } else  { loaded[param.arg] = true; } }); } if (!method.accepts) return; // Filter out parameters that are generated from the incoming request, // or generated by functions that use those resources. method.accepts = method.accepts.filter(function(arg) { if (!arg.http) return true; // Don't show derived arguments. if (typeof arg.http === 'function') return false; // Don't show arguments set to the incoming http request. // Please note that body needs to be shown, such as User.create(). if (arg.http.source === 'req' || arg.http.source === 'res' || arg.http.source === 'context') { return false; } if (arg.http.source === 'path' && arg.arg !== 'id') { if (!method.resourceParams) { method.resourceParams = []; method.hasResourceParams = true; } method.resourceParams.push(arg); } return true; }); }); c.properties = objectFilter(c.sharedClass.ctor.definition.properties, (_, propertyName) => { return (c.sharedClass.ctor.definition.settings.hidden || []).indexOf(propertyName) === -1; }); c.isUser = c.sharedClass.ctor.prototype instanceof app.loopback.User || c.sharedClass.ctor.prototype === app.loopback.User.prototype; models[name] = c; }); buildScopes(models); return models; }; var SCOPE_METHOD_REGEX = /^prototype.__([^_]+)__(.+)$/; function buildScopes(models) { for (var modelName in models) { buildScopesOfModel(models, modelName); } } function buildScopesOfModel(models, modelName) { var modelClass = models[modelName]; modelClass.scopes = {}; modelClass.methods.forEach(function (method) { buildScopeMethod(models, modelName, method); }); return modelClass; } // reverse-engineer scope method // defined by loopback-datasource-juggler/lib/scope.js function buildScopeMethod(models, modelName, method) { var modelClass = models[modelName]; var match = method.name.match(SCOPE_METHOD_REGEX); if (!match) return; var op = match[1]; var scopeName = match[2]; var modelPrototype = modelClass.sharedClass.ctor.prototype; var targetClass = modelPrototype[scopeName] && modelPrototype[scopeName]._targetClass; if (modelClass.scopes[scopeName] === undefined) { if (!targetClass) { console.error( 'Warning: scope %s.%s is missing _targetClass property.' + '\nThe Angular code for this scope won\'t be generated.' + '\nPlease upgrade to the latest version of' + '\nloopback-datasource-juggler to fix the problem.', modelName, scopeName); modelClass.scopes[scopeName] = null; return; } if (!findModelByName(models, targetClass)) { console.error( 'Warning: scope %s.%s targets class %j, which is not exposed ' + '\nvia remoting. The Angular code for this scope won\'t be generated.', modelName, scopeName, targetClass); modelClass.scopes[scopeName] = null; return; } modelClass.scopes[scopeName] = { methods: {}, targetClass: targetClass }; } else if (modelClass.scopes[scopeName] === null) { // Skip the scope, the warning was already reported return; } var apiName = scopeName; if (op == 'get') { // no-op, create the scope accessor } else if (op == 'delete') { apiName += '.destroyAll'; } else { apiName += '.' + op; } modelClass.sharedClass.ctor.relations[scopeName].targetClass = capitalize(targetClass); } function findModelByName(models, name) { for (var n in models) { if (n.toLowerCase() == name.toLowerCase()) return models[n]; } } function capitalize(string) { return string[0].toUpperCase() + string.slice(1); }