Merge branch 'release/1.1.0' into production
This commit is contained in:
commit
c70b8ac413
292
README.md
292
README.md
|
@ -4,7 +4,7 @@ LoopBack Boot is a convention-based bootstrapper for LoopBack applications.
|
|||
|
||||
**For full documentation, see the official StrongLoop documentation:**
|
||||
|
||||
* [Creating a LoopBack application](http://docs.strongloop.com/display/DOC/Creating+a+LoopBack+application)
|
||||
* [Creating a LoopBack application](http://docs.strongloop.com/display/LB/Creating+a+LoopBack+application)
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -23,5 +23,293 @@ app.use(loopback.rest());
|
|||
app.listen();
|
||||
```
|
||||
|
||||
See [API docs](http://apidocs.strongloop.com/loopback-boot/) for
|
||||
See [API docs](http://apidocs.strongloop.com/loopback-boot/#api) for
|
||||
complete API reference.
|
||||
|
||||
## Configurations and conventions
|
||||
|
||||
The bootstrapping process takes care of the following tasks:
|
||||
|
||||
- Configuration of data-sources.
|
||||
- Definition and configuration of custom Models, attaching models to
|
||||
data-sources.
|
||||
- Configuration of app settings like `host`, `port` or `restApiRoot`.
|
||||
- Running additional boot scripts to keep the custom setup code in multiple
|
||||
small files as opposed to keeping everything in the main app file.
|
||||
|
||||
Below is the typical project layout. See the following sections for description
|
||||
of the project files.
|
||||
|
||||
```
|
||||
project/
|
||||
app.js
|
||||
app.json
|
||||
datasources.json
|
||||
models.json
|
||||
models/
|
||||
boot/
|
||||
```
|
||||
|
||||
### App settings
|
||||
|
||||
The settings are loaded from the file `app.json` in the project root directory
|
||||
and can be accessed via `app.get('option-name')` from the code.
|
||||
|
||||
Additionally, the following files can provide values to override `app.json`:
|
||||
|
||||
- `app.local.js` or `app.local.json`
|
||||
- `app.{env}.js` or `app.{env}.json`, where `{env}` is the value of `NODE_ENV`
|
||||
(typically `development` or `production`)
|
||||
|
||||
**NOTE:** The additional files can override the top-level keys with
|
||||
value-types (strings, numbers) only. Nested objects and arrays are
|
||||
not supported at the moment.
|
||||
|
||||
#### Example settings
|
||||
|
||||
*app.json*
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "localhost",
|
||||
"port": 3000,
|
||||
"restApiRoot": "/api"
|
||||
}
|
||||
```
|
||||
|
||||
*app.production.js*
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
host: process.env.CUSTOM_HOST,
|
||||
port: process.env.CUSTOM_PORT
|
||||
};
|
||||
```
|
||||
|
||||
### Data sources
|
||||
|
||||
The configuration of data sources is loaded from the file `datasources.json`
|
||||
in the project root directory, the data sources can be accessed via
|
||||
`app.datasources['datasource-name']` from the code.
|
||||
|
||||
Additionally, the following files can provide values to override
|
||||
`datasources.json`:
|
||||
|
||||
- `datasources.local.js` or `datasources.local.json`
|
||||
- `datasources.{env}.js` or `datasources.{env}.json`,
|
||||
where `{env}` is the value of `NODE_ENV`
|
||||
(typically `development` or `production`)
|
||||
|
||||
**NOTE:** The additional files can override the top-level data-source options
|
||||
with value-types (strings, numbers) only. Nested objects and arrays are
|
||||
not supported at the moment.
|
||||
|
||||
#### Example data sources
|
||||
|
||||
*datasources.json*
|
||||
|
||||
```js
|
||||
{
|
||||
// the key is the datasource name
|
||||
// the value is the config object to pass to
|
||||
// app.dataSource(name, config).
|
||||
db: {
|
||||
connector: 'memory'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*datasources.production.json*
|
||||
|
||||
```js
|
||||
{
|
||||
db: {
|
||||
connector: 'mongodb',
|
||||
database: 'myapp',
|
||||
user: 'myapp',
|
||||
password: 'secret'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Models
|
||||
|
||||
App models are loaded from the file `models.json`.
|
||||
|
||||
#### Example models
|
||||
|
||||
The following is example JSON for two `Model` definitions:
|
||||
`Dealership` and `Location`.
|
||||
|
||||
```js
|
||||
{
|
||||
// the key is the model name
|
||||
"Dealership": {
|
||||
// a reference, by name, to a dataSource definition
|
||||
"dataSource": "my-db",
|
||||
// the options passed to Model.extend(name, properties, options)
|
||||
"options": {
|
||||
"relations": {
|
||||
"cars": {
|
||||
"type": "hasMany",
|
||||
"model": "Car",
|
||||
"foreignKey": "dealerId"
|
||||
}
|
||||
}
|
||||
},
|
||||
// the properties passed to Model.extend(name, properties, options)
|
||||
"properties": {
|
||||
"id": {"id": true},
|
||||
"name": "String",
|
||||
"zip": "Number",
|
||||
"address": "String"
|
||||
}
|
||||
},
|
||||
"Car": {
|
||||
"dataSource": "my-db"
|
||||
// options can be specified at the top level too
|
||||
"relations": {
|
||||
"dealer": {
|
||||
"type": "belongsTo",
|
||||
"model": "Dealership",
|
||||
"foreignKey": "dealerId"
|
||||
},
|
||||
}
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "String",
|
||||
"required": true,
|
||||
"id": true
|
||||
},
|
||||
"make": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"model": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Adding custom methods to models
|
||||
|
||||
The models created from `models.json` come with the set of built-in methods
|
||||
like `find` and `create`. To implement your custom methods, you should
|
||||
create a javascript file in `models/` directory named after the model
|
||||
and define the methods there.
|
||||
|
||||
Example:
|
||||
|
||||
*models/car.js*
|
||||
|
||||
```js
|
||||
module.exports = function(app) {
|
||||
var Car = app.models.Car;
|
||||
|
||||
Car.prototype.honk = function(duration, cb) {
|
||||
// make some noise for `duration` seconds
|
||||
cb();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Boot scripts
|
||||
|
||||
When the data sources and models are configured, the bootstrapper invokes
|
||||
all scripts in the `boot/` folder. The scripts are sorted lexicographically
|
||||
ingoring case.
|
||||
|
||||
#### Example boot script
|
||||
|
||||
*boot/authentication.js*
|
||||
|
||||
```js
|
||||
module.exports = function(app) {
|
||||
app.enableAuth();
|
||||
};
|
||||
```
|
||||
|
||||
## Running in a browser
|
||||
|
||||
The bootstrap process is implemented in two steps that can be called
|
||||
independently.
|
||||
|
||||
### Build
|
||||
|
||||
The first step loads all configuration files, merges values from additional
|
||||
config files like `app.local.js` and produces a set of instructions
|
||||
that can be used to boot the application.
|
||||
|
||||
These instructions must be included in the browser bundle together
|
||||
with all configuration scripts from `models/` and `boot/`.
|
||||
|
||||
Don't worry, you don't have to understand these details.
|
||||
Just call `boot.compileToBrowserify`, it will take care of everything for you.
|
||||
|
||||
*build file (Gruntfile.js, gulpfile.js)*
|
||||
|
||||
```js
|
||||
var browserify = require('browserify');
|
||||
var boot = require('loopback-boot');
|
||||
|
||||
var b = browserify({
|
||||
basedir: appDir,
|
||||
});
|
||||
|
||||
// add the main application file
|
||||
b.require('./browser-app.js', { expose: 'loopback-app' });
|
||||
|
||||
// add boot instructions
|
||||
boot.compileToBrowserify(appDir, b);
|
||||
|
||||
// create the bundle
|
||||
var out = fs.createWriteStream('browser-bundle.js');
|
||||
b.bundle().pipe(out);
|
||||
// handle out.on('error') and out.on('close')
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
In the browser, the main application file should call loopback-boot
|
||||
to setup the loopback application by executing the instructions
|
||||
contained in the browser bundle:
|
||||
|
||||
*browser-app.js*
|
||||
|
||||
```js
|
||||
var loopback = require('loopback');
|
||||
var boot = require('loopback-boot');
|
||||
|
||||
var app = module.exports = loopback();
|
||||
boot(app);
|
||||
```
|
||||
|
||||
The app object created above can be accessed via `require('loopback-app')`,
|
||||
where `loopback-app` is the identifier used for the main app file in
|
||||
the browserify build shown above.
|
||||
|
||||
Here is a simple example demonstrating the concept:
|
||||
|
||||
*index.html*
|
||||
|
||||
```xml
|
||||
<script src="app.bundle.js"></script>
|
||||
<script>
|
||||
var app = require('loopback-app');
|
||||
var User = app.models.User;
|
||||
|
||||
User.login(
|
||||
{ email: 'test@example.com', password: '12345' },
|
||||
function(err, res) {
|
||||
if (err) {
|
||||
console.error('Login failed: ', err);
|
||||
} else {
|
||||
console.log('Logged in.');
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
```
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
{
|
||||
"content": [
|
||||
"README.md",
|
||||
{
|
||||
"title": "Bootstrap API",
|
||||
"title": "API",
|
||||
"depth": 2
|
||||
},
|
||||
"index.js",
|
||||
"browser.js",
|
||||
"docs/configuration.md",
|
||||
"docs/browserify.md"
|
||||
"browser.js"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
## Running in a browser
|
||||
|
||||
The bootstrap process is implemented in two steps that can be called
|
||||
independently.
|
||||
|
||||
### Build
|
||||
|
||||
The first step loads all configuration files, merges values from additional
|
||||
config files like `app.local.js` and produces a set of instructions
|
||||
that can be used to boot the application.
|
||||
|
||||
These instructions must be included in the browser bundle together
|
||||
with all configuration scripts from `models/` and `boot/`.
|
||||
|
||||
Don't worry, you don't have to understand these details.
|
||||
Just call `boot.compileToBrowserify`, it will take care of everything for you.
|
||||
|
||||
```js
|
||||
/*-- build file --*/
|
||||
var browserify = require('browserify');
|
||||
var boot = require('loopback-boot');
|
||||
|
||||
var b = browserify({
|
||||
basedir: appDir,
|
||||
});
|
||||
|
||||
// add the main application file
|
||||
b.require('./app.js', { expose: 'loopback-app' });
|
||||
|
||||
// add boot instructions
|
||||
boot.compileToBrowserify(appDir, b);
|
||||
|
||||
// create the bundle
|
||||
var out = fs.createWriteStream('app.bundle.js');
|
||||
b.bundle().pipe(out);
|
||||
// handle out.on('error') and out.on('close')
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
In the browser, the main application file should call loopback-boot
|
||||
to setup the loopback application by executing the instructions
|
||||
contained in the browser bundle:
|
||||
|
||||
```js
|
||||
/*-- app.js --*/
|
||||
var loopback = require('loopback');
|
||||
var boot = require('loopback-boot');
|
||||
|
||||
var app = module.exports = loopback();
|
||||
boot(app);
|
||||
```
|
||||
|
||||
The app object created above can be accessed via `require('loopback-app')`,
|
||||
where `loopback-app` is the identifier used for the main app file in
|
||||
the browserify build shown above.
|
||||
|
||||
Here is a simple example demonstrating the concept:
|
||||
|
||||
```xml
|
||||
<script src="app.bundle.js"></script>
|
||||
<script>
|
||||
var app = require('loopback-app');
|
||||
var User = app.models.User;
|
||||
|
||||
User.login({ email: 'test@example.com', password: '12345', function(err, res) {
|
||||
if (err) {
|
||||
console.error('Login failed: ', err);
|
||||
} else {
|
||||
console.log('Logged in.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
|
@ -1,50 +0,0 @@
|
|||
## Configuration and conventions
|
||||
|
||||
### Model Definitions
|
||||
|
||||
The following is example JSON for two `Model` definitions:
|
||||
"dealership" and "location".
|
||||
|
||||
```js
|
||||
{
|
||||
"dealership": {
|
||||
// a reference, by name, to a dataSource definition
|
||||
"dataSource": "my-db",
|
||||
// the options passed to Model.extend(name, properties, options)
|
||||
"options": {
|
||||
"relations": {
|
||||
"cars": {
|
||||
"type": "hasMany",
|
||||
"model": "Car",
|
||||
"foreignKey": "dealerId"
|
||||
}
|
||||
}
|
||||
},
|
||||
// the properties passed to Model.extend(name, properties, options)
|
||||
"properties": {
|
||||
"id": {"id": true},
|
||||
"name": "String",
|
||||
"zip": "Number",
|
||||
"address": "String"
|
||||
}
|
||||
},
|
||||
"car": {
|
||||
"dataSource": "my-db"
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "String",
|
||||
"required": true,
|
||||
"id": true
|
||||
},
|
||||
"make": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"model": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
11
index.js
11
index.js
|
@ -7,6 +7,10 @@ var addInstructionsToBrowserify = require('./lib/bundler');
|
|||
* Initialize an application from an options object or
|
||||
* a set of JSON and JavaScript files.
|
||||
*
|
||||
* > **NOTE**: This module is primarily intended for use with LoopBack 2.0.
|
||||
* It _does_ work with LoopBack 1.x applications, but
|
||||
* none of the LoopBack 1.x examples or generated code (scaffolding) use it.
|
||||
*
|
||||
* This function takes an optional argument that is either a string
|
||||
* or an object.
|
||||
*
|
||||
|
@ -32,9 +36,10 @@ var addInstructionsToBrowserify = require('./lib/bundler');
|
|||
* In both cases, the function loads JavaScript files in the `/models` and
|
||||
* `/boot` subdirectories of the application root directory with `require()`.
|
||||
*
|
||||
* **NOTE:** mixing `app.boot()` and `app.model(name, config)` in multiple
|
||||
* files may result in models being **undefined** due to race conditions.
|
||||
* To avoid this when using `app.boot()` make sure all models are passed
|
||||
* **NOTE:** Mixing `bootLoopBackApp(app, bootConfig)` and
|
||||
* `app.model(name, modelConfig)` in multiple
|
||||
* files may result in models being undefined due to race conditions.
|
||||
* To avoid this when using `bootLoopBackApp()` make sure all models are passed
|
||||
* as part of the `models` definition.
|
||||
*
|
||||
* Throws an error if the config object is not valid or if boot fails.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var assert = require('assert');
|
||||
var _ = require('underscore');
|
||||
var loopback = require('loopback');
|
||||
var semver = require('semver');
|
||||
var debug = require('debug')('loopback:boot:executor');
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,9 @@ var debug = require('debug')('loopback:boot:executor');
|
|||
*/
|
||||
|
||||
module.exports = function execute(app, instructions) {
|
||||
patchAppLoopback(app);
|
||||
assertLoopBackVersion(app);
|
||||
|
||||
setHost(app, instructions);
|
||||
setPort(app, instructions);
|
||||
setApiRoot(app, instructions);
|
||||
|
@ -20,13 +23,42 @@ module.exports = function execute(app, instructions) {
|
|||
|
||||
setupDataSources(app, instructions);
|
||||
setupModels(app, instructions);
|
||||
autoAttach();
|
||||
autoAttach(app);
|
||||
|
||||
runBootScripts(app, instructions);
|
||||
|
||||
enableAnonymousSwagger(app, instructions);
|
||||
};
|
||||
|
||||
function patchAppLoopback(app) {
|
||||
if (app.loopback) return;
|
||||
// app.loopback was introduced in 1.9.0
|
||||
// patch the app object to make loopback-boot work with older versions too
|
||||
try {
|
||||
app.loopback = require('loopback');
|
||||
} catch(err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND') {
|
||||
console.error(
|
||||
'When using loopback-boot with loopback <1.9, '+
|
||||
'the loopback module must be available for `require(\'loopback\')`.');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function assertLoopBackVersion(app) {
|
||||
var RANGE = '1.x || 2.x';
|
||||
|
||||
var loopback = app.loopback;
|
||||
if (!semver.satisfies(loopback.version || '1.0.0', RANGE)) {
|
||||
throw new Error(
|
||||
'The `app` is powered by an incompatible loopback version %s. ' +
|
||||
'Supported versions: %s',
|
||||
loopback.version || '(unknown)',
|
||||
RANGE);
|
||||
}
|
||||
}
|
||||
|
||||
function setHost(app, instructions) {
|
||||
//jshint camelcase:false
|
||||
var host =
|
||||
|
@ -115,14 +147,14 @@ function runScripts(app, list) {
|
|||
if (!list || !list.length) return;
|
||||
list.forEach(function(filepath) {
|
||||
var exports = tryRequire(filepath);
|
||||
if (isFunctionNotModelCtor(exports))
|
||||
if (isFunctionNotModelCtor(exports, app.loopback.Model))
|
||||
exports(app);
|
||||
});
|
||||
}
|
||||
|
||||
function isFunctionNotModelCtor(fn) {
|
||||
function isFunctionNotModelCtor(fn, Model) {
|
||||
return typeof fn === 'function' &&
|
||||
!(fn.prototype instanceof loopback.Model);
|
||||
!(fn.prototype instanceof Model);
|
||||
}
|
||||
|
||||
function tryRequire(modulePath) {
|
||||
|
@ -139,9 +171,9 @@ function tryRequire(modulePath) {
|
|||
}
|
||||
|
||||
// Deprecated, will be removed soon
|
||||
function autoAttach() {
|
||||
function autoAttach(app) {
|
||||
try {
|
||||
loopback.autoAttach();
|
||||
app.loopback.autoAttach();
|
||||
} catch(e) {
|
||||
if(e.name === 'AssertionError') {
|
||||
console.warn(e);
|
||||
|
|
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-boot",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Convention-based bootstrapper for LoopBack applications",
|
||||
"keywords": [
|
||||
"StrongLoop",
|
||||
|
@ -10,7 +10,7 @@
|
|||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/loobpack/loopback-boot"
|
||||
"url": "https://github.com/strongloop/loopback-boot"
|
||||
},
|
||||
"main": "index.js",
|
||||
"browser": "browser.js",
|
||||
|
@ -23,9 +23,10 @@
|
|||
"url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE"
|
||||
},
|
||||
"dependencies": {
|
||||
"underscore": "^1.6.0",
|
||||
"commondir": "0.0.1",
|
||||
"debug": "^0.8.1",
|
||||
"commondir": "0.0.1"
|
||||
"semver": "^2.3.0",
|
||||
"underscore": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"loopback": "^1.5.0",
|
||||
|
@ -34,8 +35,5 @@
|
|||
"supertest": "^0.13.0",
|
||||
"fs-extra": "^0.9.1",
|
||||
"browserify": "^4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"loopback": "1.x || 2.x"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue