682 lines
19 KiB
Markdown
682 lines
19 KiB
Markdown
# asteroid
|
|
v0.7.0
|
|
|
|
## Install
|
|
|
|
slnode install asteroid -g
|
|
|
|
## Server API
|
|
|
|
- [App](#app)
|
|
- [Model](#model)
|
|
- [DataSource](#data-source)
|
|
- [Connectors](#connectors)
|
|
- [GeoPoint](#geo-point)
|
|
- [Asteroid Types](#asteroid-types)
|
|
- [REST Router](#rest-router)
|
|
|
|
## Client API
|
|
|
|
_TODO_
|
|
|
|
### App
|
|
|
|
Create an asteroid application.
|
|
|
|
var asteroid = require('asteroid');
|
|
var app = asteroid();
|
|
|
|
app.get('/', function(req, res){
|
|
res.send('hello world');
|
|
});
|
|
|
|
app.listen(3000);
|
|
|
|
**Notes:**
|
|
|
|
- extends [express](http://expressjs.com/api.html#express)
|
|
- see [express docs](http://expressjs.com/api.html) for details
|
|
- supports [express / connect middleware](http://expressjs.com/api.html#middleware)
|
|
|
|
#### app.model(Model)
|
|
|
|
Expose a `Model` to remote clients.
|
|
|
|
var memory = asteroid.createDataSource({connector: 'memory'});
|
|
var Color = memory.defineModel({name: String});
|
|
|
|
app.model(Color);
|
|
app.use(asteroid.rest());
|
|
|
|
**Note:** this will expose all [shared methods](#shared-methods) on the model.
|
|
|
|
#### app.models()
|
|
|
|
Get the app's exposed models.
|
|
|
|
var models = app.models();
|
|
|
|
models.forEach(function (Model) {
|
|
console.log(Model.name); // color
|
|
});
|
|
|
|
### Model
|
|
|
|
An Asteroid `Model` is a vanilla JavaScript class constructor with an attached set of properties and settings. A `Model` instance is created by passing a data object containing properties to the `Model` constructor.
|
|
|
|
var Color = asteroid.createModel({name: 'string'});
|
|
var red = new Color({name: 'red'});
|
|
|
|
**Properties**
|
|
|
|
A model defines a list of property names, types and other validation metadata. A [DataSource](#data-source) uses this definition to validate a `Model` during operations such as `save()`.
|
|
|
|
**Settings**
|
|
|
|
Some [DataSources](#data-source) may support additional `Model` settings.
|
|
|
|
Define an asteroid model.
|
|
|
|
var User = asteroid.createModel('user', {
|
|
first: String,
|
|
last: String,
|
|
age: Number
|
|
});
|
|
|
|
#### Model.validatesPresenceOf(properties...)
|
|
|
|
Require a model to include a property to be considered valid.
|
|
|
|
User.validatesPresenceOf('first', 'last', 'age');
|
|
|
|
#### Model.validatesLengthOf(property, options)
|
|
|
|
Require a property length to be within a specified range.
|
|
|
|
User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});
|
|
|
|
#### Model.validatesInclusionOf(property, options)
|
|
|
|
Require a value for `property` to be in the specified array.
|
|
|
|
User.validatesInclusionOf('gender', {in: ['male', 'female']});
|
|
|
|
#### Model.validatesExclusionOf(property, options)
|
|
|
|
Require a value for `property` to not exist in the specified array.
|
|
|
|
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
|
|
|
|
#### Model.validatesNumericalityOf(property, options)
|
|
|
|
Require a value for `property` to be a specific type of `Number`.
|
|
|
|
User.validatesNumericalityOf('age', {int: true});
|
|
|
|
#### Model.validatesUniquenessOf(property, options)
|
|
|
|
Ensure the value for `property` is unique.
|
|
|
|
User.validatesUniquenessOf('email', {message: 'email is not unique'});
|
|
|
|
**Note:** not available for all [connectors](#connectors).
|
|
|
|
#### myModel.isValid()
|
|
|
|
Validate the model instance.
|
|
|
|
user.isValid(function (valid) {
|
|
if (!valid) {
|
|
user.errors // hash of errors {attr: [errmessage, errmessage, ...], attr: ...}
|
|
}
|
|
});
|
|
|
|
#### Model.attachTo(dataSource)
|
|
|
|
Attach a model to a [DataSource](#data-source). Attaching a [DataSource](#data-source) updates the model with additional methods and behaviors.
|
|
|
|
var oracle = asteroid.createDataSource({
|
|
connector: 'oracle',
|
|
host: '111.22.333.44',
|
|
database: 'MYDB',
|
|
username: 'username',
|
|
password: 'password'
|
|
});
|
|
|
|
User.attachTo(oracle);
|
|
|
|
**Note:** until a model is attached to a data source it will **not** have any **attached methods**.
|
|
|
|
#### Attached Methods
|
|
|
|
Attached methods are added by attaching a vanilla model to a data source with a connector. Each [connector](#connectors) enables its own set of operations that are attached to a `Model` as methods. To see available methods for a data source with a connector call `dataSource.operations()`.
|
|
|
|
##### Model.create([data], [callback])
|
|
|
|
Create an instance of Model with given data and save to the attached data source.
|
|
|
|
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
|
console.log(user instanceof User); // true
|
|
});
|
|
|
|
##### model.save([options], [callback])
|
|
|
|
Save an instance of a Model to the attached data source.
|
|
|
|
var joe = new User({first: 'Joe', last: 'Bob'});
|
|
joe.save(function(err, user) {
|
|
if(user.errors) {
|
|
console.log(user.errors);
|
|
} else {
|
|
console.log(user.id);
|
|
}
|
|
});
|
|
|
|
##### model.updateAttributes(data, [callback])
|
|
|
|
Save specified attributes to the attached data source.
|
|
|
|
user.updateAttributes({
|
|
first: 'updatedFirst',
|
|
name: 'updatedLast'
|
|
}, fn);
|
|
|
|
##### model.upsert(data, callback)
|
|
|
|
Update when record with id=data.id found, insert otherwise. **Note:** no setters, validations or hooks applied when using upsert.
|
|
|
|
##### model.destroy([callback])
|
|
|
|
Remove a model from the attached data source.
|
|
|
|
model.destroy(function(err) {
|
|
// model instance destroyed
|
|
});
|
|
|
|
##### Model.destroyAll(callback)
|
|
|
|
Delete all Model instances from data source. **Note:** destroyAll method does not perform destroy hooks.
|
|
|
|
##### Model.find(id, callback)
|
|
|
|
Find instance by id.
|
|
|
|
User.find(23, function(err, user) {
|
|
console.info(user.id); // 23
|
|
});
|
|
|
|
Model.all(filter, callback);
|
|
|
|
Find all instances of Model, matched by query. Fields used for filter and sort should be declared with `{index: true}` in model definition.
|
|
|
|
**filter**
|
|
|
|
- **where** `Object` { key: val, key2: {gt: 'val2'}}
|
|
- **include** `String`, `Object` or `Array`.
|
|
- **order** `String`
|
|
- **limit** `Number`
|
|
- **skip** `Number`
|
|
|
|
Find the second page of 10 users over age 21 in descending order.
|
|
|
|
User.all({where: {age: {gt: 21}}, order: 'age DESC', limit: 10, skip: 10})
|
|
|
|
**Note:** See the specific connector's [docs](#connectors) for more info.
|
|
|
|
##### Model.count([query], callback)
|
|
|
|
Query count of Model instances in data source. Optional query param allows to count filtered set of Model instances.
|
|
|
|
User.count({approved: true}, function(err, count) {
|
|
console.log(count); // 2081
|
|
});
|
|
|
|
#### Static Methods
|
|
|
|
Define a static model method.
|
|
|
|
User.login = function (username, password, fn) {
|
|
var passwordHash = hashPassword(password);
|
|
this.findOne({username: username}, function (err, user) {
|
|
var failErr = new Error('login failed');
|
|
|
|
if(err) {
|
|
fn(err);
|
|
} else if(!user) {
|
|
fn(failErr);
|
|
} else if(user.password === passwordHash) {
|
|
MySessionModel.create({userId: user.id}, function (err, session) {
|
|
fn(null, session.id);
|
|
});
|
|
} else {
|
|
fn(failErr);
|
|
}
|
|
});
|
|
}
|
|
|
|
Expose the static model method to clients as a [remote method](#remote-method).
|
|
|
|
asteroid.remoteMethod(
|
|
User,
|
|
User.login,
|
|
{
|
|
accepts: [
|
|
{arg: 'username', type: 'string', required: true},
|
|
{arg: 'password', type: 'string', required: true}
|
|
],
|
|
returns: {arg: 'sessionId', type: 'any'},
|
|
http: {path: '/sign-in'}
|
|
}
|
|
);
|
|
|
|
#### Instance Methods
|
|
|
|
Define an instance method.
|
|
|
|
User.prototype.logout = function (fn) {
|
|
MySessionModel.destroyAll({userId: this.id}, fn);
|
|
}
|
|
|
|
Define a remote model instance method.
|
|
|
|
asteroid.remoteMethod(User, User.prototype.logout);
|
|
|
|
#### Remote Methods
|
|
|
|
Both instance and static methods can be exposed to clients. A remote method must accept a callback with the conventional `fn(err, result, ...)` signature.
|
|
|
|
##### asteroid.remoteMethod(Model, fn, [options]);
|
|
|
|
Expose a remote method.
|
|
|
|
Product.stats = function(fn) {
|
|
myApi.getStats('products', fn);
|
|
}
|
|
|
|
asteroid.remoteMethod(
|
|
Product,
|
|
Product.stats,
|
|
{
|
|
returns: {arg: 'stats', type: 'array'},
|
|
http: {path: '/info', verb: 'get'}
|
|
}
|
|
);
|
|
|
|
**Options**
|
|
|
|
- **accepts** - (optional) an arguments description specifying the remote method's arguments. A
|
|
- **returns** - (optional) an arguments description specifying the remote methods callback arguments.
|
|
- **http** - (advanced / optional, object) http routing info
|
|
- **http.path** - the relative path the method will be exposed at. May be a path fragment (eg. '/:myArg') which will be populated by an arg of the same name in the accepts description.
|
|
- **http.verb** - (get, post, put, del, all) - the route verb the method will be available from.
|
|
|
|
**Argument Description**
|
|
|
|
An arguments description defines either a single argument as an object or an ordered set of arguments as an array.
|
|
|
|
// examples
|
|
{arg: 'myArg', type: 'number'}
|
|
|
|
[
|
|
{arg: 'arg1', type: 'number', required: true},
|
|
{arg: 'arg2', type: 'array'}
|
|
]
|
|
|
|
**Types**
|
|
|
|
Each argument may define any of the [asteroid types](#asteroid-types).
|
|
|
|
**Notes:**
|
|
|
|
- The callback is an assumed argument and does not need to be specified in the accepts array.
|
|
- The err argument is also assumed and does not need to be specified in the returns array.
|
|
|
|
#### Hooks
|
|
|
|
Run a function before or after a model method is called.
|
|
|
|
User.before('save', function(user, next) {
|
|
console.log('about to save', user);
|
|
|
|
next();
|
|
});
|
|
|
|
Prevent the method from being called by passing an error to `next()`.
|
|
|
|
User.before('delete', function(user, next) {
|
|
// prevent all delete calls
|
|
next(new Error('deleting is disabled'));
|
|
});
|
|
|
|
#### Remote Hooks
|
|
|
|
Run a function before or after a remote method is called by a client.
|
|
|
|
User.beforeRemote('save', function(ctx, user, next) {
|
|
if(ctx.user.id === user.id) {
|
|
next();
|
|
} else {
|
|
next(new Error('must be logged in to update'))
|
|
}
|
|
});
|
|
|
|
#### Context
|
|
|
|
Remote hooks are provided with a Context `ctx` that contains raw access to the transport specific objects. The `ctx` object also has a set of consistent apis that are consistent across transports.
|
|
|
|
##### ctx.me
|
|
|
|
The id of the user calling the method remotely. **Note:** this is undefined if a user is not logged in.
|
|
|
|
##### Rest
|
|
|
|
When [asteroid.rest](#asteroidrest) is used the following `ctx` properties are available.
|
|
|
|
###### ctx.req
|
|
|
|
The express ServerRequest object. [See full documentation](http://expressjs.com/api.html#req).
|
|
|
|
###### ctx.res
|
|
|
|
The express ServerResponse object. [See full documentation](http://expressjs.com/api.html#res).
|
|
|
|
Access the raw `req` object for the remote method call.
|
|
|
|
#### Relationships
|
|
|
|
##### Model.hasMany(Model)
|
|
|
|
Define a "one to many" relationship.
|
|
|
|
// by referencing model
|
|
Book.hasMany(Chapter);
|
|
// specify the name
|
|
Book.hasMany('chapters', {model: Chapter});
|
|
|
|
Query and create the related models.
|
|
|
|
Book.create(function(err, book) {
|
|
// using 'chapters' scope for build:
|
|
var c = book.chapters.build({name: 'Chapter 1'});
|
|
|
|
// same as:
|
|
c = new Chapter({name: 'Chapter 1', bookId: book.id});
|
|
|
|
// using 'chapters' scope for create:
|
|
book.chapters.create();
|
|
|
|
// same as:
|
|
Chapter.create({bookId: book.id});
|
|
|
|
// using scope for querying:
|
|
book.chapters(function(err, chapters) {
|
|
/* all chapters with bookId = book.id */
|
|
});
|
|
|
|
book.chapters({where: {name: 'test'}, function(err, chapters) {
|
|
// all chapters with bookId = book.id and name = 'test'
|
|
});
|
|
});
|
|
|
|
##### Model.hasAndBelongsToMany()
|
|
|
|
TODO: implement / document
|
|
|
|
#### Model.availableHooks()
|
|
|
|
Return a list of available hooks.
|
|
|
|
console.log(User.availableHooks()); // ['save', ...]
|
|
|
|
#### Shared Methods
|
|
|
|
Any static or instance method can be decorated as `shared`. These methods are exposed over the provided transport (eg. [asteroid.rest](#rest)).
|
|
|
|
#### Model.availableMethods()
|
|
|
|
Returns the currently available api of a model as well as descriptions of any modified behavior or methods from attached data sources.
|
|
|
|
User.attachTo(oracle);
|
|
console.log(User.availableMethods());
|
|
|
|
Output:
|
|
|
|
{
|
|
'User.all': {
|
|
accepts: [{arg: 'filter', type: 'object', description: '...'}],
|
|
returns: [{arg: 'users', type: ['User']}]
|
|
},
|
|
'User.find': {
|
|
accepts: [{arg: 'id', type: 'any'}],
|
|
returns: [{arg: 'items', type: 'User'}]
|
|
},
|
|
...
|
|
}
|
|
|
|
### Data Source
|
|
|
|
An Asteroid `DataSource` provides [Models](#model) with the ability to manipulate data. Attaching a `DataSource` to a `Model` adds [instance methods](#instance-methods) and [static methods](#static-methods) to the `Model`. The added methods may be [remote methods](#remote-methods).
|
|
|
|
Define a data source for persisting models.
|
|
|
|
var oracle = asteroid.createDataSource({
|
|
connector: 'oracle',
|
|
host: '111.22.333.44',
|
|
database: 'MYDB',
|
|
username: 'username',
|
|
password: 'password'
|
|
});
|
|
|
|
#### dataSource.createModel(name, options, settings)
|
|
|
|
Define a model and attach it to a `DataSource`.
|
|
|
|
var Color = oracle.createModel('color', {name: String});
|
|
|
|
#### dataSource.discover(options, fn)
|
|
|
|
Discover an object containing properties and settings for an existing data source.
|
|
|
|
oracle.discover({owner: 'MYORG'}, function(err, tables) {
|
|
var productSchema = tables.PRODUCTS;
|
|
var ProductModel = oracle.createModel('product', productSchema.properties, productSchema.settings);
|
|
});
|
|
|
|
#### dataSource.discoverSync(options)
|
|
|
|
Synchronously discover an object containing properties and settings for an existing data source tables or collections.
|
|
|
|
var tables = oracle.discover({owner: 'MYORG'});
|
|
var productSchema = tables.PRODUCTS;
|
|
var ProductModel = oracle.createModel('product', productSchema.properties, productSchema.settings);
|
|
|
|
#### dataSource.discoverModels(options, fn)
|
|
|
|
Discover a set of models based on tables or collections in a data source.
|
|
|
|
oracle.discoverModels({owner: 'MYORG'}, function(err, models) {
|
|
var ProductModel = models.Product;
|
|
});
|
|
|
|
**Note:** The `models` will contain all properties and settings discovered from the data source. It will also automatically discover and create relationships.
|
|
|
|
#### dataSource.discoverModelsSync(options)
|
|
|
|
Synchronously Discover a set of models based on tables or collections in a data source.
|
|
|
|
var models = oracle.discoverModels({owner: 'MYORG'});
|
|
var ProductModel = models.Product;
|
|
|
|
#### dataSource.enable(operation)
|
|
|
|
Enable a data source operation. Each [connector](#connector) has its own set of set enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
|
|
|
// all rest data source operations are
|
|
// disabled by default
|
|
var rest = asteroid.createDataSource({
|
|
connector: require('asteroid-rest'),
|
|
url: 'http://maps.googleapis.com/maps/api'
|
|
enableAll: true
|
|
});
|
|
|
|
// enable an operation
|
|
twitter.enable('find');
|
|
|
|
// enable remote access
|
|
twitter.enableRemote('find')
|
|
|
|
**Notes:**
|
|
|
|
- only enabled operations will be added to attached models
|
|
- data sources must enable / disable operations before attaching or creating models
|
|
|
|
#### dataSource.disable(operation)
|
|
|
|
Disable a data source operation. Each [connector](#connector) has its own set of set enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
|
|
|
// all rest data source operations are
|
|
// disabled by default
|
|
var oracle = asteroid.createDataSource({
|
|
connector: require('asteroid-oracle'),
|
|
host: '...',
|
|
...
|
|
});
|
|
|
|
// disable an operation completely
|
|
oracle.disable('destroyAll');
|
|
|
|
// or only disable it as a remote method
|
|
oracle.disableRemote('destroyAll');
|
|
|
|
**Notes:**
|
|
|
|
- disabled operations will not be added to attached models
|
|
- disabling the remoting for a method only affects client access (it will still be available from server models)
|
|
- data sources must enable / disable operations before attaching or creating models
|
|
|
|
#### dataSource.operations()
|
|
|
|
List the enabled and disabled operations.
|
|
|
|
console.log(oracle.operations());
|
|
|
|
Output:
|
|
|
|
{
|
|
find: {
|
|
allowRemote: true,
|
|
accepts: [...],
|
|
returns: [...]
|
|
enabled: true
|
|
},
|
|
...
|
|
}
|
|
|
|
#### Connectors
|
|
|
|
Create a data source with a specific connector. See **available connectors** for specific connector documentation.
|
|
|
|
var memory = asteroid.createDataSource({
|
|
connector: require('asteroid-memory')
|
|
});
|
|
|
|
**Available Connectors**
|
|
|
|
- [Oracle](http://github.com/strongloop/asteroid-connectors/oracle)
|
|
- [In Memory](http://github.com/strongloop/asteroid-connectors/memory)
|
|
- TODO - [REST](http://github.com/strongloop/asteroid-connectors/rest)
|
|
- TODO - [MySQL](http://github.com/strongloop/asteroid-connectors/mysql)
|
|
- TODO - [SQLite3](http://github.com/strongloop/asteroid-connectors/sqlite)
|
|
- TODO - [Postgres](http://github.com/strongloop/asteroid-connectors/postgres)
|
|
- TODO - [Redis](http://github.com/strongloop/asteroid-connectors/redis)
|
|
- TODO - [MongoDB](http://github.com/strongloop/asteroid-connectors/mongo)
|
|
- TODO - [CouchDB](http://github.com/strongloop/asteroid-connectors/couch)
|
|
- TODO - [Firebird](http://github.com/strongloop/asteroid-connectors/firebird)
|
|
|
|
**Installing Connectors**
|
|
|
|
Include the connector in your package.json dependencies and run `npm install`.
|
|
|
|
{
|
|
"dependencies": {
|
|
"asteroid-oracle": "latest"
|
|
}
|
|
}
|
|
|
|
### GeoPoint
|
|
|
|
Embed a latitude / longitude point in a [Model](#model).
|
|
|
|
var CoffeeShop = asteroid.createModel('coffee-shop', {
|
|
location: 'GeoPoint'
|
|
});
|
|
|
|
Asteroid Model's with a GeoPoint property and an attached DataSource may be queried using geo spatial filters and sorting.
|
|
|
|
Find the 3 nearest coffee shops.
|
|
|
|
CoffeeShop.attach(oracle);
|
|
var here = new GeoPoint({lat: 10.32424, long: 5.84978});
|
|
CoffeeShop.all({where: {location: {near: here}}}, function(err, nearbyShops) {
|
|
console.info(nearbyShops); // [CoffeeShop, ...]
|
|
});
|
|
|
|
#### geoPoint.distanceTo(geoPoint, options)
|
|
|
|
Get the distance to another `GeoPoint`.
|
|
|
|
var here = new GeoPoint({lat: 10, long: 10});
|
|
var there = new GeoPoint({lat: 5, long: 5});
|
|
console.log(here.distanceTo(there, {type: 'miles'})); // 438
|
|
|
|
#### GeoPoint.distanceBetween(a, b, options)
|
|
|
|
Get the distance between two points.
|
|
|
|
GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438
|
|
|
|
#### Distance Types
|
|
|
|
- `miles`
|
|
- `radians`
|
|
- `kilometers`
|
|
|
|
#### geoPoint.lat
|
|
|
|
The latitude point in degrees. Range: -90 to 90.
|
|
|
|
#### geoPoint.long
|
|
|
|
The longitude point in degrees. Range: -180 to 180.
|
|
|
|
### Asteroid Types
|
|
|
|
Various APIs in Asteroid accept type descriptions (eg. [remote methods](#remote-methods), [asteroid.createModel()](#model)). The following is a list of supported types.
|
|
|
|
- `null` - JSON null
|
|
- `Boolean` - JSON boolean
|
|
- `Number` - JSON number
|
|
- `String` - JSON string
|
|
- `Object` - JSON object
|
|
- `Array` - JSON array
|
|
- `Date` - a JavaScript date object
|
|
- `Buffer` - a node.js Buffer object
|
|
- [GeoPoint](#geopoint) - an asteroid GeoPoint object.
|
|
|
|
### REST Router
|
|
|
|
Expose models over rest using the `asteroid.rest` router.
|
|
|
|
app.use(asteroid.rest());
|
|
|
|
**REST Documentation**
|
|
|
|
View generated REST documentation by visiting: [http://localhost:3000/_docs](http://localhost:3000/_docs).
|
|
|
|
### SocketIO Middleware **Not Available**
|
|
|
|
**Coming Soon** - Expose models over socket.io using the `asteroid.sio()` middleware.
|
|
|
|
app.use(asteroid.sio);
|
|
|