@mean-expert/loopback-sdk-builder
Version:
Tool for auto-generating Software Development Kits (SDKs) for LoopBack
214 lines (193 loc) • 7 kB
JavaScript
/**
* 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);
}