Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,25 @@ connector.saveToFile = function() {

connector.loadFromFile = function() {
var cb = arguments[arguments.length - 1];
var loader = connector.loader;
var recursiveCall = !!connector.loader;

if(loader) {
loader.once('complete', cb);
loader.once('error', cb);
return;
if (!recursiveCall) {
connector.loader = new EventEmitter();
connector.loader.setMaxListeners(100);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whats up with this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests were complaining about a possible leak when 11 listeners were registered.

}

loader = connector.loader = new EventEmitter();
var loader = connector.loader;
loader.once('complete', cb);
loader.once('error', cb);

if (recursiveCall) return;

var done = function(err) {
if (err)
loader.emit('error', err);
else
loader.emit('complete');

connector.loader = null;
cb(err);
};

// reset the cache
Expand Down
3 changes: 2 additions & 1 deletion models.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"PackageDefinition": {
"properties": {
"componentName": {"type": "string", "required": true}
},
"public": false,
"dataSource": "db",
Expand Down Expand Up @@ -94,7 +95,7 @@
"scopes": "array",
"public": "boolean",
"indexes": "array",
"componentName": "string",
"componentName": {"type": "string", "required": true},
"dataSource": "string"
},
"public": true,
Expand Down
92 changes: 60 additions & 32 deletions models/component-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,15 @@ ComponentDefinition.loadIntoCache = function(cache, componentName, components, c
cb();
});
} else {
debug('component configFile does not exist');
steps.push(function(cb) {
var componentData = {
name: componentName,
configFile: path.join(componentName, 'config.json')
};
debug('adding to cache component entry [%s]', componentData.configFile);
ComponentDefinition.addToCache(cache, componentName, componentData);
cb();
});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to modify the workspace entities, rename ComponentDefinition to Component and move the content of config.json to a new entity ComponentConfig.

That way we won't have to create a dummy ComponentDefinition when there is not config.json in the component, as is the case with the root component of api-server.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the only issue that we are generating an empty config.json?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not generating any config.json file.

I also don't like that config.json is embedded in ComponentDefinition, since config.json does not contain anything related to component definition. Instead, it contains arbitrary component configuration used by the component itself.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's continue the discussion in #66.

}

if(componentModels) {
Expand Down Expand Up @@ -147,12 +155,17 @@ ComponentDefinition.saveToFs = function(cache, componentDef, cb) {

var debug = require('debug')('workspace:component:save:' + componentName);

var configFile = ComponentDefinition.getConfigFile(componentName, componentDef);
// remove extra data that shouldn't be persisted to the fs
delete componentDef.configFile;
configFile.data = componentDef;
var hasApp = ComponentDefinition.hasApp(componentDef);

if (hasApp) {
var configFile = ComponentDefinition.getConfigFile(componentName, componentDef);
// remove extra data that shouldn't be persisted to the fs
delete componentDef.configFile;
delete componentDef.name;
configFile.data = componentDef;

filesToSave.push(configFile);
filesToSave.push(configFile);
}

PackageDefinition.allFromCache(cache).forEach(function(package) {
if(package.componentName === componentName) {
Expand All @@ -165,34 +178,41 @@ ComponentDefinition.saveToFs = function(cache, componentDef, cb) {
}
});

var dataSoureConfig = {};
var dataSourcePath = path.join(componentName, 'datasources.json');
var cachedDataSources = DataSourceDefinition.allFromCache(cache);

cachedDataSources.forEach(function(dataSourceDef) {
if(dataSourceDef.componentName === componentName) {
dataSourcePath = DataSourceDefinition.getPath(componentName, dataSourceDef);
dataSoureConfig[dataSourceDef.name] = dataSourceDef;
delete dataSourceDef.name;
}
});

filesToSave.push(new ConfigFile({
path: dataSourcePath,
data: dataSoureConfig
}));

var cachedComponentModels = ComponentModel.allFromCache(cache);
var componentModelsPath = path.join(componentName, ComponentModel.settings.defaultConfigFile);
var componentModelFile = new ConfigFile({path: componentModelsPath}); // models.json
var componentModelsConfig = componentModelFile.data = {};
if (hasApp) {
var dataSoureConfig = {};
var dataSourcePath = path.join(componentName, 'datasources.json');
var cachedDataSources = DataSourceDefinition.allFromCache(cache);

cachedDataSources.forEach(function(dataSourceDef) {
if (dataSourceDef.componentName === componentName) {
dataSourcePath = DataSourceDefinition.getPath(componentName, dataSourceDef);
dataSoureConfig[dataSourceDef.name] = dataSourceDef;
// remove extra data that shouldn't be persisted to the fs
delete dataSourceDef.name;
delete dataSourceDef.componentName;
}
});

cachedComponentModels.forEach(function(componentModel) {
componentModelsConfig[componentModel.name] = componentModel;
delete componentModel.name;
});
filesToSave.push(new ConfigFile({
path: dataSourcePath,
data: dataSoureConfig
}));

var cachedComponentModels = ComponentModel.allFromCache(cache);
var componentModelsPath = path.join(componentName, ComponentModel.settings.defaultConfigFile);
var componentModelFile = new ConfigFile({path: componentModelsPath}); // models.json
var componentModelsConfig = componentModelFile.data = {};

cachedComponentModels.forEach(function(componentModel) {
if (componentModel.componentName !== componentName) return;
componentModelsConfig[componentModel.name] = componentModel;
// remove extra data that shouldn't be persisted to the fs
delete componentModel.name;
delete componentModel.componentName;
});

filesToSave.push(componentModelFile);
filesToSave.push(componentModelFile);
}

var cachedModels = ModelDefinition.allFromCache(cache);

Expand All @@ -216,3 +236,11 @@ ComponentDefinition.saveToFs = function(cache, componentDef, cb) {
cb();
});
}

ComponentDefinition.hasApp = function(componentDef) {
// At the moment, the root component does not have `app.js`,
// all other components (rest, server) have their app.js
// In the future, we should read this from component,
// e.g. package.json > loopback-workspace > app: true|false
return componentDef.name !== '.';
};
4 changes: 3 additions & 1 deletion models/component-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ var ComponentModel = app.models.ComponentModel;

/**
* - `name` is required and must be unique per `ComponentDefinition`
* - `componentName` is required and must refer to an existing component
*
* @header Property Validation
*/

ComponentModel.validatesUniquenessOf('name', { scopedTo: ['component'] });
ComponentModel.validatesPresenceOf('name');
ComponentModel.validatesPresenceOf('name');
ComponentModel.validatesPresenceOf('componentName');
3 changes: 3 additions & 0 deletions models/config-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ ConfigFile.prototype.load = function(cb) {
function load(exists, cb) {
if(exists) {
fs.readJson(absolutePath, function(err, data) {
if (err && err.name === 'SyntaxError') {
err.message = 'Cannot parse ' + configFile.path + ': ' + err.message;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the error code to ensure it is a parser error. Otherwise the error might be confusing (eg. too many fds open, permissions, ENOENT, etc).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isn't a good error code for JSON parsing errors AFAICT.

Perhaps it is enough to change the message to 'Cannot load ' + configFile.path + ...?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a check err.name === 'SyntaxError'.

}
cb(err, err ? undefined : data);
});
} else {
Expand Down
4 changes: 3 additions & 1 deletion models/data-source-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ var DataSourceDefinition = app.models.DataSourceDefinition;
/**
* - `name` must be unique per `ComponentDefinition`
* - `name` and `connector` are required
*
* - `componentName` is required and must refer to an existing component
*
* @header Property Validation
*/

DataSourceDefinition.validatesUniquenessOf('name', { scopedTo: ['componentName'] });
DataSourceDefinition.validatesPresenceOf('name', 'connector');
DataSourceDefinition.validatesPresenceOf('componentName');

/**
* Test the datasource definition connection.
Expand Down
23 changes: 22 additions & 1 deletion models/workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Workspace.addComponent = function(options, cb) {
var template;
var templateName = options.template || DEFAULT_TEMPLATE;
var name = options.name || templateName;
var packageName = options.packageName || name;
if(options.root) name = '.';
var fileTemplatesDir = path.join(TEMPLATE_DIR, templateName, 'template');

Expand Down Expand Up @@ -84,7 +85,7 @@ Workspace.addComponent = function(options, cb) {
}

if(template.package) {
template.package.name = name;
template.package.name = packageName;
setComponentName(template.package);
steps.push(function(cb) {
PackageDefinition.create(template.package, cb);
Expand Down Expand Up @@ -179,3 +180,23 @@ var staticConnectorList = require('../available-connectors');
Workspace.listAvailableConnectors = function(cb) {
cb(null, staticConnectorList);
};

/**
* Check if the project is a valid directory.
* The callback is called with no arguments when the project is valid.
* @param {function(Error=)} cb
*/
Workspace.isValidDir = function(cb) {
// Every call of `Model.find()` triggers reload from the filesystem
// This allows us to catch basic errors in config files
ComponentDefinition.find(function(err, list) {
if (err) {
cb(err);
} else if (!list.length) {
cb(new Error('Invalid workspace: no components found.'));
} else {
// TODO(bajtos) Add more sophisticated validation based on component types
cb();
}
});
};
4 changes: 3 additions & 1 deletion templates/api-server/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

var template = module.exports;
var component = template.component = {
restApiRoot: '/api'
};

template.config = {
Expand All @@ -15,7 +14,10 @@ template.package = {
"version": "0.0.0",
"main": "server/server.js",
"dependencies": {
"compression": "^1.0.3",
"errorhandler": "^1.1.1",
"loopback": "~2.0.0-beta3",
"loopback-boot": "~2.0.0-beta1",
"loopback-datasource-juggler": "~2.0.0-beta2"
},
"optionalDependencies": {
Expand Down
40 changes: 13 additions & 27 deletions templates/rest/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,37 @@ template.package = {

template.componentModels = [
{
name: 'user',
name: 'User',
dataSource: 'db'
},
{
name: 'user',
dataSource: 'db'
name: 'AccessToken',
dataSource: 'db',
public: false
},
{
name: 'acl',
dataSource: 'db'
name: 'ACL',
dataSource: 'db',
public: false
},
{
name: 'roleMapping',
dataSource: 'db'
name: 'RoleMapping',
dataSource: 'db',
public: false
},
{
name: 'role',
dataSource: 'db'
name: 'Role',
dataSource: 'db',
public: false
}
];

template.relations = [
{
fromModel: 'user',
model: 'access-token',
type: 'hasMany',
foreignKey: 'userId'
},
{
fromModel: 'role',
type: 'hasMany',
model: 'roleMapping',
foreignKey: 'roleId'
}
];

template.datasources = [
{
name: 'db',
defaultForType: 'db',
connector: 'memory'
},
{
name: 'mail',
defaultForType: 'mail',
connector: 'mail'
}
];
8 changes: 4 additions & 4 deletions templates/rest/template/boot/authentication.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var rest = require('../rest');

// enable authentication
rest.enableAuth();
module.exports = function enableAuthentication(rest) {
// enable authentication
rest.enableAuth();
};
1 change: 1 addition & 0 deletions templates/server/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

var template = module.exports;
var component = template.component = {
restApiRoot: '/api',
host: 'localhost'
};

Expand Down
23 changes: 23 additions & 0 deletions templates/server/template/boot/explorer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = function mountLoopBackExplorer(server) {
var explorer;
try {
explorer = require('loopback-explorer');
} catch(err) {
console.log(
'Run `npm install loopback-explorer` to enable the LoopBack explorer'
);
return;
}

var restApp = require('../../rest');
var restApiRoot = server.get('restApiRoot');

var explorerApp = explorer(restApp, { basePath: restApiRoot });
server.use('/explorer', explorerApp);
server.once('started', function(baseUrl) {
// express 4.x (loopback 2.x) uses `mountpath`
// express 3.x (loopback 1.x) uses `route`
var explorerPath = explorerApp.mountpath || explorerApp.route;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
});
};
5 changes: 5 additions & 0 deletions templates/server/template/boot/rest-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = function mountRestApi(server) {
var restApp = require('../../rest');
var restApiRoot = server.get('restApiRoot');
server.use(restApiRoot, restApp);
};
Loading