-
Notifications
You must be signed in to change notification settings - Fork 2
Old documentation
_ _ _ _ _
| \ | | ___ __| |_ _| | __ _| |_ ___ _ __
| \| |/ _ \ / _` | | | | |/ _` | __/ _ \| '__|
| |\ | (_) | (_| | |_| | | (_| | || (_) | |
|_| \_|\___/ \__,_|\__,_|_|\__,_|\__\___/|_| V0.0.19
Nodulator
is designed to make it more easy to create highly modulable
applications, built with REST APIs and with integrated ORM in CoffeeScript.
You must understand express basics for routing
Open exemples folder to see a full working exemple in JavaScript, CoffeeScript and LiveScript.
Released under GPLv2
Following documentation is almost deprecated or incomplete and will be removed soon. Please prefer the most updated documentation at http://nodulator.champii.io
- Philosophy
- Features
- Compatible modules
- Installation
- Quick Start (TL;DR)
- Configuration
- Resource
- Route
- Wrappers
- Reactive Values
- DB Systems
- Other Stuff
- Modules
- Project Generation
- Developers
- Contributors
- TODO
- Changelog
Nodulator
is a project that is trying to make a big overlay to every
traditional packages used to make REST client/server applications in CoffeeScript.
Its main goal is to give developers a complex REST routing system, an ORM and high-level modules, encapsulating every classic behaviour needed to create complex projects.
Its core provides everything needed to build powerfull and highly modulable REST APIs, and allow the developer to reuse his code through every projects.
With this framework, you will never loose 10 or 20 hours anymore boostraping a project from scratch or looking for the right technology to implement.
You will never have headache anymore trying to combine socket.io
and passport
to keep track of your session with your sockets (for exemple),
or you will never have to consider assets management,
and with the integrated Project Generation you will never
need to manage your Nodulator
modules dependencies.
You need to add authentication logic to your open/public API ? Look for Nodulator-Account !
You need to add socket.io support ? Look for Nodulator-Socket !
If you don't find your desired module, just build it !
Nodulator
is like a lego game, instead of learning how to use a given
technology and how to combine it with thoses you often use,
it allows you to manipulate simple concepts like adding a Account
concept to
your application(for exemple), and so adding authentication and permission logic to your app.
Also, each brick or layer of a Nodulator
application is highly linked to every others.
For exemple, when you add Nodulator-Account
module to your app, if you have
already included Nodulator-Angular
it will automaticaly add everything needed
to handle angular authentication (it will add a separate view, some directives
and a user service). Have you added Nodulator-Socket
?
So Nodulator-Angular
will also be highly linked to your server's models,
by providing a socket interface to your server Resource
.
Check the Jump To section !
- LiveScript
- Integrated ORM
- Integrated Routing system (with express, and highly linked with ORM)
- Multiple DB Systems
- Complex inheritance system
- Chainable async calls
- Modulable
- Project generation
- Cache
- Schema-less/Schema-full models
- Model validation
- Model association (rails style) and automatic retrieval
- Models and associations over different DB systems
- Reactive values Hacktiv
- Promises or Callbacks
- Fliped callback parameters
- Log and Debug system
- Console mode
-
Nodulator-Assets:
- Automatic assets management
-
Nodulator-Socket:
- Socket.io implementation for Nodulator
-
Nodulator-Angular:
- Angular implementation for Nodulator
- Inheritance system
- Integrated and linked SocketIO
- Assets management
-
Nodulator-Account:
- Authentication with passport
- Permissions management
- Sessions
- Nodulator-Angular integration
Just run :
npm install nodulator
Or check the Project Generation section
After you can require Nodulator
as a module :
N = require 'nodulator'
Here is the quickiest way to play around Nodulator
_ = require 'underscore'
N = require 'nodulator'
class PlayerRoute extends N.Route.MultiRoute
Config: ->
# We create: GET => /api/1/{resource_name}/usernames
# Get a list of every players' usernames
# There is a @resource property, containing attached Resource class
@Get '/usernames' ~> @resource.ListUsernames!
# We call super() to apply N.Route.MultiRoute behaviour
# We called '/usernames' route before, so it won't be override by
# default route GET => /api/1/{resource_name}/:id
super!
# We create: PUT => /api/1/{resource_name}/:id/levelUp
@Put '/:id/levelUp' ~> it.instance.LevelUp!
# We create a resource, and we attach the PlayerRoute
class Players extends N 'player', PlayerRoute
# We create a LevelUp method
# If you dont provide done callback,
# the wrap promise will give one to fill and return as promise
LevelUp: @_WrapPromise (done) ->
@level++
@Save done
# And a class method to get a list of usernames
@ListUsernames: @_WrapPromise (done) ->
@List (err, players) ->
return done err if err?
done null, _(players).pluck 'username'
Go inside your project folder, copy this POC in a test.coffee
file and type in:
$> coffee test.coffee
It will run your project on port 3000
by default
Then open your favorite REST API Client (Postman for Chrome is my favorite)
and try the following routes :
(Assuming full url is always of the following form : "http://localhost:3000/api/1/[...]")
Each route is of the following form :
{VERB} {URL} ({PARAMS}) => {ANSWER}
POST '/api/1/players' {username: 'test1', level: 1} => {id: 1, username: 'test1', level: 1}
POST '/api/1/players' {username: 'test2', level: 1} => {id: 2, username: 'test2', level: 1}
GET '/api/1/players' => [{id: 1, username: 'test1', level: 1},
{id: 2, username: 'test2', level: 1}]
GET '/api/1/players/1' => {id: 1, username: 'test1', level: 1}
GET '/api/1/players/2' => {id: 2, username: 'test2', level: 1}
PUT '/api/1/players/2/levelUp' {} => {id: 2, username: 'test2', level: 2}
PUT '/api/1/players/2/levelUp' {} => {id: 2, username: 'test2', level: 3}
GET '/api/1/players/usernames' => ['test1', 'test2']
PUT '/api/1/players/2' {username: 'notAUsername'} => {id: 2, username: 'notAUsername', level: 3}
GET '/api/1/players/usernames' => ['test1', 'notAUsername']
DELETE '/api/1/players/1' {} => {id: 1, username: 'test1', level: 1}
GET '/api/1/players/usernames' => ['notAUsername']
First of all, the configuration process is absolutely optional.
If you don't give Nodulator a config, it will assume you want to use SqlMem DB system, with no persistance at all. Usefull for heavy tests periods.
If you prefere to use a persistant system, here is the procedure :
N = require 'nodulator'
N.Config
dbType: 'Mongo' # You can select 'SqlMem' or 'Mongo' or 'Mysql'
dbAuth: # Fields needed if Mongo or Mysql
host: 'localhost'
database: 'test'
port: 27017 # From there, can be ignored. Default values taken
user: 'test' # |
pass: 'test' # |_
You can also provide a 'store' property in order to use Redis
to manage sessions:
N = require 'nodulator'
N.Config
store:
type: 'redis'
host: 'localhost' # <- default value, can be ignored
If ommited, sessions will be memory based (not recommended)
You can flip the arguments of the default done
callback by specifying a flipDone: true
parameter
N.Config
flipDone: true
Nodulator
provides 2 main Objects :
N
N.Route
A Resource
is a class permitting to retrive and save a model from a DB.
Here is an exemple of creating a Resource
Players = N 'player'
Here, it creates a PlayerResource
, linked with a players
table in DB (if any)
You can pass several params to N
:
N name [, Route] [, config]
You can attach a Route and/or a config object to a Resource
.
This call prepare the Resource
. All the Nodulator
's magic is
inside this call.
It is now optional to call Init(), as it will be called by the first Resource query.
However, note that the Init() call can take the config parameter, in case of crossing Resource schema definition. You must call it befor the first query.
Also, it returns the whole Resource for chaining purpose.
Each Resource
provides some 'Class methods' to manage the specific model in db :
Players.Create(blob, done)
Players.Fetch(constraints, done)
Players.List(constraints, done)
Players.Delete(constraints, done)
The Create
method, like its name suggests, can add a row to a given Resource.
Players.Create {login: 'player1', age: 24}, (err, player) ->
return console.error err if err?
[...] # Do something with player instance
Players.Create [
{login: 'player1', age: 24}
{login: 'player2', age: 68}
{login: 'player3', age: 34}
], (err, players) -> [...]
It automaticaly adds an id
row to every new instance. Don't try to override it !
The Fetch
method can take an id and return a Players
instance to done
callback :
It can take an id, an object or an array
Players.Fetch 1, (err, player) ->
return console.error err if err?
[...] # Do something with player instance
Players.Fetch [1, 5], (err, players) -> [...]
Players.Fetch {login: 'value'}, (err, player) -> [...]
Players.Fetch [7, {age: 25}, 9, {login: 'test'}], (err, players) -> [...]
The Fetch
will return the first found row that pass the given object equality
You can list every models from this Resource
thanks to List
call :
Players.List (err, players) ->
return console.error err if err?
[...] # players is an array of every Players instance
Players.List {age: 26}, (err, players) -> [...]
Players.List [{age: 26}, {age: 52}], (err, players) -> [...]
# Be carefull, as List(obj) returns an array, List(array) returns an Array of Array
You can delete the same way, except that Delete callback only have one error parameter.
Be carefull, The Delete
will erase the first found row that pass the given object equality
Players.Delete 1, (err) ->
return console.error err if err?
Players.Delete {login: 'toto'}, (err) -> [...]
Players.Delete [9, {login: 'toto'}, 4, {age: 22}], (err) -> [...]
Never use new
operator directly on a Resource
, else you might bypass the
relationning system.
A player instance has some methods :
player.Save(done)
Save the model in DB. The callback take 2 arguments : (err, instance) ->
player.Delete(done)
Delete the model from the DB. The callback take 1 argument : (err) ->
player.Serialize()
Get every object properties, and return it in a new object.
Generaly used to get what to be saved in DB.
player.ToJSON()
By default, it calls Serialize().
Generaly used to get what to send to client.
player.ExtendSafe(blob)
This method extend the Resource with the given blob,
But it keeps safe every Associations and vital stuff.
Every Instance and Class methods can be chained, they all returns their 'this'. (Except when using Promises, read the corresponding chapter)
Every call that take a done
callback can be expressed by Promises:
Players.Fetch 1, (err, player) ->
return console.error err if err?
[...] # Do something with player instance
Is equivalent to :
Players.Fetch 1
.then (player) -> [...] # Do something with player instance
.fail (err) -> console.error err
If you omit the callback, it will return a Promise. If you pass a callback, it will return this
for chaining purpose.
By default, every Resource
is schema less. It means that you can put almost
anything into your Resource
.
It can obviously be schema less only for DB systems that allows it. When using MySQL for exemple, you'll have to define a schema and validation rules if you don't want your server to answer raw SQL errors for non existant fields
To make a Resource
to respect a given schema, you just have to define a
schema
field into Resource
configuration
config =
schema:
toto: 'array' #This can be an array of everything
test: ['int'] #This MUST be an array of integer
foo: 'int'
bar:
type: 'string'
optional: true
foobar:
type: 'string'
default: 'foobar'
Differents types are
- bool
- int
- string
- date
- array
By default, each fields is required, but you can make one field optional with
the optional
field to true
or presence of default
field. It will never
complain if this field is not present, but if it is, it will check for its validity.
You can specify a type directly with a string, assuming that the given property will be required:
config =
schema:
foo: 'int'
bar: 'string'
If you specify a default
field, the Resource
will auto-set its property
if the value is not given.
For exemple, with this Resource config :
config =
schema:
foo:
type: 'int'
default: 5
If I dont provide a 'foo' value at Resource instanciation, it will take the one given by the 'default' field
You can put a function in place of a default value. In this case, this function will be executed to get the default value at instanciation time.
config =
schema:
foo:
type: 'int'
default: (obj) ->
You can make associations between Resource
. For making a Resource
to be
automaticaly fetched when querying another, you can add it to its schema :
Bars = N 'bar', {schema: barProperty: 'string'}
config =
schema:
foo: 'int'
barId: 'int'
bar:
type: Bars
localKey: 'barId'
Tests = N 'test', config
# Fetch Tests with id == 1
Tests.Fetch 1, (err, test) ->
# Will be for exemple :
# {
# id: 1,
# foo: 12,
# barId: 1,
# bar: {id: 1, barProperty: 'test'}
# }
If you want to retrive a collection of resource, you can wrap types in arrays instead:
config =
schema:
foo: 'int'
barIds: ['int']
bar:
type: [Bars]
localKey: 'barIds'
Tests = N 'test', config
# Fetch Tests with id == 1
Tests.Fetch 1, (err, test) ->
# Will be for exemple :
# {
# id: 1,
# foo: 12,
# barIds: [1, 2],
# bar: [{id: 1, barProperty: 'test'},
# {id: 2, barProperty: 'test2'}]
# }
When you want to retrieve a Resource based on an id property inside the object, you must use the 'localKey' property. It will fetch for the given Resource Type with the corresponding id from 'localKey' field
If can also retrieve a Resource that have a property containing the actual instance id:
Bars = N 'bar', {schema: testId: 'int'}
config =
schema:
foo: 'int'
bar:
type: Bars
distantKey: 'testId'
Tests = N 'test', config
Tests.Create {foo: 12}
.then -> Bars.Create {testId: 1} #We assume its the first created resource
.then -> Tests.Fetch 1 #We know its the first resource created
.then (test) -> #Here test have a 'bar' property with testId == 1
Given the last exemple, when you get a Test instance with id == 1, it will have a field 'bar' with the Bars instance that have testId == 1
When you have associated Resources, you can choose the depth of your queries.
By default, the depth is at 1 for every Resource. It means that it will fetch only one level of Resource maximum.
If you want a deeper level of retrieving, you can set the 'maxDepth' property of a Resource configuration:
config =
maxDepth: 3
Or if you prefer change it at runtime and make it inheritable, you can act on the Resource.DEFAULT_DEPTH field.
Note that the config one has priority.
You can inherit from a Resource
to override or enhance its default behaviour,
or to make a complex class inheritance system built on Resource
In CoffeeScript its pretty easy:
class Units extends N 'unit'
# We create a new instance method
LevelUp: (done) ->
@level++
@Save done
# We override default 'List' method
@List: (done) ->
@ListBy {life: 10}, (err, units) ->
return done err if err?
done null, units
You can define an abstract class, that won't be attached to any model in DB or
any Route
class Units extends N 'unit', {abstract: true}
[...]
Of course, abstract classes are only designed to be inherited. (Please note that
they can't have a Route
attached)
Given the last exemple, here is a class that inherits from Units
# Note the call to 'Extend()' method
class Players extends Units.Extend 'player'
# Give Players a new beheviour
NewBehaviour: (args, done) ->
[...]
# Overriding existing Units LevelUp()
LevelUp: (done) ->
[...]
You can call the Extend() method either from a full Resource
or from an
abstract
one.
Please note that if both parent and child are full Resource
, both will have
corresponding model available from ORM (here units
and players
)
So be carefull when creating extended Resource
, and think about abstract
!
Nodulator
provides a Route
object, which can to be attached to a Resource
object (or not)
in order to describe routing process.
There is 2 different ways to attach a Resource
to a Route
:
You can bind a Route
to a Resource
class Units extends N 'unit', N.Route
Or you can bind a Resource
to a Route
class UnitRoute extends N.Route
resource: Units
#In this case, you have to instanciate the Route yourself once
route = new UnitRoute
Every Route
can be initiated and configured when its attached Resource
is,
else you must instantiate one yourself.
Default N.Route
do nothing. You have to inherit from it to describe routes :
class UnitRoute extends N.Route
# Override the Config() method
Config: ->
# And never forget to call the super()
super()
# Here we define: GET => /api/1/{resource_name}/:id
@Get '/:id', (req, res) =>
# The @resource field points to attached Resource
@resource.Fetch req.params.id, (err, unit) ->
return res.status(500).send err if err?
res.status(200).send unit.ToJSON()
# Here we define: POST => /api/1/{resource_name}
@Post (req, res) ->
res.status(200).end()
This Route
, attached to a Resource
(here Units
) add 2 endpoints :
GET => /api/1/units/:id
POST => /api/1/units
Each Route
have to implement a Config()
method, calling super()
and
defining routes thanks to 'verbs' route calls (@Get(), @Post(), @Put(), @Delete(), @All()).
Here are all 'verb' route calls definition :
N.Route.All [endPoint = '/'], [middleware, [middleware, ...]], callback
N.Route.Get [endPoint = '/'], [middleware, [middleware, ...]], callback
N.Route.Post [endPoint = '/'], [middleware, [middleware, ...]], callback
N.Route.Put [endPoint = '/'], [middleware, [middleware, ...]], callback
N.Route.Delete [endPoint = '/'], [middleware, [middleware, ...]], callback
Nodulator provides a predefined route system for lazy, adapted for Singleton
Resource
: N.Route.SingleRoute
.
It setups 2 routes (exemple when attached to a Players
) :
GET => /api/1/player => Fetch
PUT => /api/1/player => Update
This route system needs to have a resource with id == 1
in your actual DB
before startup time to work.
If you don't have a config.schema
property set in your Resource
, it will
create one for you at startup time.
Else, Nodulator
will throw an error and shutdown.
If you use SqlMem
DB system, you must add a 'default' value to each resource
fields in order to add it at startup.
Nodulator provides also a standard route system for lazy : N.Route.MultiRoute
.
It allows you to handle your resources like its a big collection.
It setups 5 routes (exemple when attached to a Players
) :
GET => /api/1/players => List
POST => /api/1/players => Create
GET => /api/1/players/:id => Get One
PUT => /api/1/players/:id => Update
DELETE => /api/1/players/:id => Delete
Note that the first GET call accept query parameters for selection.
You can inherit from any route object :
class Test1Route extends N.Route
class Test2Route extends N.Route.MultiRoute
class Test3Route extends Test2Route
class Test4Route extends Test3Route
And you can override existing route by providing same association verb + url. Exemple :
class TestRoute extends N.Route.MultiRoute
Config: ->
super()
# Here we override the default GET => /api/1/{resource_name}/:id
@Get '/:id', (req, res) =>
[...]
You can declare Route that dont belongs to any Resource. Instead, you define yourself every endpoints :
class TestRoute extends N.Route
Config: ->
super()
@Get (req, res) =>
res.status(200).send {elem: 'value'}
# You have to give them a name, to replace the one a Resource would have given
route = new TestRoute 'test'
Create the following routes :
GET => /api/1/tests
See Hacktiv documentation for more informations
Reactive programing benefits don't have to be proven. It just works.
You can watch for a fonction and it will be re-executed when reactive values inside have changed.
The following exemple will print successively 1 and 2
val = new N.Watch.Value 1
# The function is executed a first time
N.Watch ->
console.log val()
# This call will cause the function to be reexecuted.
val 2
You just saw that Nodulator provide the default Hacktiv library.
You can also watch for data-sets to change to retrigger the function :
Tests = N 'test'
[...]
N.Watch ->
Tests.Fetch 1, (err, test) ->
Tests.Fetch 1
.then (test) ->
test.prop = 'value'
# This call will cause the watching function to be reexecuted.
test.Save()
It works also with List:
N.Watch ->
# Get a double array
Tests.List [
{prop1: 'value1'}
{prop1: 'value2'}
], (err, test) ->
[...]
Tests.Fetch {prop1: 'value1'}
.then (test) ->
test.prop = 'value3'
# This call will cause the watching function to be reexecuted.
test.Save()
Every Resource have his own reactive Error property that can be watched:
N.Watch ->
console.error Tests.error()
# will produce an error and refresh the console.log above
Tests.Fetch {unknownProp: 'val'}, (err, test) ->
Very usefull with flipDone: true
option.
We defined a driver interface for some DB implementations.
It's based on SQL Table
concept. (see lib/connectors/sql/index.coffee)
Table.Find(id, done)
Table.FindWhere(fields, where, done)
Table.Select(fields, where, options, done)
Table.Save(blob, done)
Table.Insert(blob, done)
Table.Update(blob, where, done)
Table.Delete(id, done)
Every Resource
have an associated Table
instance that links to the good
table/document in the good DB driver system
Built-in MySQL
implementation (node-mysql)
for Nodulator
Check lib/connectors/sql/Mysql.coffee
Built-in MongoDB
implementation (mongous)
for Nodulator
Check lib/connectors/sql/Mongo.coffee
Special DB driver, built on RAM.
It provides same options as others systems do, but nothing is stored. When you stop the server, everything is deleted.
Check lib/connectors/sql/SqlMem.coffee
There is a N.bus
object that is basicaly an EventEmitter
. Every
objects in Nodulator
use this bus.
Here are the emitted events:
-
On a new
Resource
being inserted in DB, sends it after aSerialize()
callN.bus.emit 'new_' + resource_name, @Serialize()
-
On a
Resource
being updated in DB, sends it after aSerialize()
callN.bus.emit 'update_' + resource_name, @Serialize()
-
On a
Resource
being deleted from DB, sends it after aSerialize()
callN.bus.emit 'delete_' + resource_name, @Serialize()
Exemple
Players = N 'player'
N.on 'new_player', (player) ->
[...] # Do something with this brand new player
You can override default Bus
by setting new class to N.Bus :
N = require 'nodulator'
NewBus = require './NewBus'
N.Bus = NewBus
Always set new Bus
before any new Resource
call or any added Module
To inject a module into Nodulator
, preceed this way :
N = require 'nodulator'
ModuleName = require 'nodulator-ModuleName'
N.Use ModuleName
Replace ModuleName
with the module's name you want to load
If you want to create a new module for Nodulator
, you have to export a single
function, taking Nodulator
as parameter :
module.exports = (Nodulator) ->
[...] # Your module here
You can extend anything you want, as the whole Nodulator
object is passed to your function.
Be carefull to server/loadOrder.json
.
Watch how other modules are made !
You can get global Nodulator
:
$> npm install -g nodulator
$> Nodulator
Usage: Nodulator (init) | ((install | install-dev | remove) moduleName1 [, moduleName2 [, ...]])
Nodulator provides a way of installing Nodulator
, modules and dependencies easely
# If no arguments, install or remove Nodulator
$> Nodulator install
$> Nodulator install-dev
$> Nodulator remove
# Will install nodulator-angular and every dependencies (if any)
$> Nodulator install angular
# Will install nodulator-angular, nodulator-account, and all their dependencies (if any)
$> Nodulator install angular account
# Will create local link instead of a full install of nodulator-angular and every dependencies (if any)
# It's used to avoid reinstalling locally a Nodulator package under development
$> Nodulator install-dev angular
# Will remove nodulator-socket
$> Nodulator remove socket
Then you can launch the init
process :
$> Nodulator init
It creates the following structure if non-existant :
main.coffee
package.json
settings/
server/
├── index.coffee
├── loadOrder.json
├── processors/
│ └── index.coffee
└── resources/
└── index.coffee
And then find for every Nodulator
modules installed, and call their
respective init
method.
It generate a main.coffee
and a package.json
with every modules pre-loaded.
The server
folder is auto-loaded (check server/index.coffee
and every
index.coffee
in subfolders).
Folders load order is defined in server/loadOrder.json
, and is automaticaly
managed by new modules installed (they care of the order)
You can immediately start to write Resource
in server/resources
!
Never forget that I'm always available at [email protected] for any questions (Job related or not ;-)
By order of priority
- Paginated Resource
- When cache expire, remove correspondant Watcher /!\
- Inherit associations (care for abstract Resources)
- Remove an existing route easely
- Better query on Resource (gt, gte, lt, lte, not, range, ...)
- Migration system
- Auto wrap new methods in
Resource
- Association Polymorphism
- Watch a specific field
- Relations not only based on id but on every property types
- Persistant sessions in Console
- 'Unique' field
- 0bject OwnRoute that perform from logged user (/api/1/player or /api/1/tasks for exemple)
- Decentralize modules config
- Scaling (cluster, distributed bus)
- Split Resource into smaller Classes
- List return a resource that can act on each item (Set, Add, ...) ( Extend Array ? )
- Better tests
- Request
- Multi Driver fetch/list
- Db
- SqlMem
- Mysql
- Mongo
- Ids
- HABTM tables
- Cache
- Config oveloading
- Schema
- HasOne
- HasMany
- HasOneThrough
- HasManyThrough
- HasAndBelongsToMany
28/11/15: 0.1.0
- LiveScript ! \o/
- Lib Folder reorganisation
- Added benchmarks folder
- Replaced @instance in Route by req._instance.
- Added a Request class to handle Resource in Route
- The Route class can take a Resource as property. Also, Routes can be Instanciated.
- Changed every 'Nodulator' call by 'N', more readable
- Added a debug system with 'debug'
- Console mode to "connect" to an existing/running Nodulator instance and perform standard calls
- Joined Every modules into this git repo for a stronger compatibility
- Shortcut the N.Resource() into a simple N()
- Added a 'Watch' for both Instance and Class of Resource, that allow more flexibility
- Added a virtual field filled when the resource is.
- Added HasOne, HasMany, BelongsTo and BelongsToMany calls. Buggy at the moment and not standard
- Added _WrapDebug() Wrapper
- Web server now starts only when needed (when first Route is being declared)
- Fixed validation fails
- Added _CreateUnwrapped
- Added a instance.Watch() call, to make the instance to auto-update when part of it change
- Schema 'strict' or 'free'
- When in Association, if the localKey doesnt exists, it is created on the fly
- Better HasOne, HasMany and BelongsTo.
- HasOneThrough, HasManyThrough
- HasAndBelongsToMany
- Resources can override global db config to put different models on different db systems
- Resource instance can be created on different db than default
- Added Cache over Redis
- Added @Hydrate() function to populate properties and associations from cache
- Added configuration for cache
- Better configuration for db
- Internal driver is now fixed to be the default Nodulator driver.
- Globalized ids management by external table
- internal_ids are now stored on default driver and are automaticaly updated with last Ids values
- Chainable calls !
- Create can now take a promise instead of an id
- Better Remove() for MayHas*() associations
- Schema is now inherited by copy
- Internal() Field property that is not put in the JSON produced by ToJSON(), so not sent to any client but saved to DB anyway
- JSON and object validation type
- Each resource is available through N.Resourcename (exemple for 'player' : N.Player)
- When a route is attached to a Resource, it is now available as Resource.Route
21/07/15: v0.0.19
- Added
SingleRoute
object, for manipulating SingletonResource
- Removed
req.instances
from everyRoute
- Added tests for
SingleRoute
- Route proxy methods for
@_All()
are now generated at runtime - Renamed
DefaultRoute
toMultiRoute
- Added a
default
field to config schema -
Resource.Init()
now returns theResource
itself, for chaining purpose. - Added tests for resource association
- Tests are now executed in specific order
- You can now give an array as schema type for a field, in order to retrive multiple resources based on id
- Added Javascript support
- Added an output line to tell the user when the framework is listening and to which port
- Fetch and Create can now take one argument or an array of arguments
- Fixed bugs on resource association:
- ToJSON() now call child ToJSON() instead of Serialize()
- ToJSON() call check if given association exists
- Added 'distantKey' in relational schema to fetch relation that have that resource id as given key
- Added maxDepth field to resource config in order to limit the relationnal fetch. There is also a Resource.DEFAULT_DEPTH constant that is used when nothing is precised.
- Added argument to Resource.Init(): You can give the config object in order to avoid recursive require when two way model association
- Removed Doc section. It will be on the website documentation.
- Code in Init() has been splited for code clarity
- Load order has changed between resources and socket
- Added a @_type variable defining the typename of an instance
- Fixed a bug in model association: no field in schema if array with no 'type'
- Improved 'arrayOf' type check
- Added function to default schema value (is that a possible virtual field ?)
- MultiRoute::Get() now can take query arguments
- Added Resource::ExtendSafe() method to preserve associated models while extending a Resource
- Modified Route::MultiRoute and Route::SingleRoute to use Resource::ExtendSafe()
- Removed app parameter from Route constructor
- Route classes can now be instanciated without any Resources
- Removed ListBy and FetchBy for simplicity
- Resource::Deserialize() is now a private call : Resource::_Deserialize()
- Removed the mandatory Init function call !
- Added Promises if no callback given.
- Routes are now instantiated when attached, not when Init. This helps the new lazy Init system
- List can now take an array
- Added Hacktiv support for Resources
- Added a ChangeWatcher for Resources that watch for the result of a query to make change
- You can now add a
flipDone: true
to the N.Config() call to have callback like (data, err) -> - Added Wrappers class to regroup every wrappers.
- Added Wrappers for Promises, FlipDone, and for WatchArgs
- Extend now dont need to be abstract to work
- Added tests for promisesm FlipDone and reactive watching
04/05/15: v0.0.18
- You can specify a 'store' general config property in order to switch to redis-based sessions
03/05/15: v0.0.17
- You can now specify a property type in schema without wrapping it in a object like {type: "string"}
15/04/15: v0.0.16
- Removed redis references for sessions
14/04/15: v0.0.15
- Minor changes in
Route
to fit Nodulator-Account new release
10/04/15: v0.0.14
- Resource 'user' is no longer a reserved word
- Resources with name finishing with 'y' are now correctly put in plural form in route name
09/04/15: v0.0.13
- Better model association and validation
- Pre-fetched resources in
Route.All()
are now put in@instance
instead ofreq[@resource.lname]
- Updated README
- Updated Mongo driver
20/01/15: v0.0.12
- Fixed bug on FetchBy
20/01/15: v0.0.11
- Fixed tests
- Added travis support for tests
- Added model associations
- Added schema and model validation
- Changed
FetchBy
andListBy
prototype. Now take an object instead of a key/value pair. - Added
Create()
method intoResource
- Added
limit
andoffset
to both Mysql and SqlMem
07/01/15: v0.0.10
- Added Philosophy section
- Added multiple package name support in package generation
- Fixed some bugs with modules
03/01/15: v0.0.9
- Separated
AccountResource
into a new module Nodulator-Account - Changed README
02/01/15: v0.0.8
- Fixed Route middleware issue
02/01/15: v0.0.7
- Separated
Socket
into a new module Nodulator-Socket - Added new methods for
@Get()
,@Post()
,@Delete()
,@Put()
,@All()
inRoute
- Replace old method
@All()
into@_All()
. Is now a private call. - Improved README (added Modules section)
- Global
Nodulator
now manage dependencies installation