UNPKG

40.5 kBMarkdownView Raw
1 _ _ _ _ _
2 | \ | | ___ __| |_ _| | __ _| |_ ___ _ __
3 | \| |/ _ \ / _` | | | | |/ _` | __/ _ \| '__|
4 | |\ | (_) | (_| | |_| | | (_| | || (_) | |
5 |_| \_|\___/ \__,_|\__,_|_|\__,_|\__\___/|_| V0.0.19
6
7[![Build Status](https://travis-ci.org/Champii/N.svg?branch=master)](https://travis-ci.org/Champii/Nodulator) (Master)
8
9[![Build Status](https://travis-ci.org/Champii/N.svg?branch=develop)](https://travis-ci.org/Champii/Nodulator) (Develop)
10
11[![NPM](https://nodei.co/npm/nodulator.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/nodulator/)
12
13[![NPM](https://nodei.co/npm-dl/nodulator.png?months=1)](https://nodei.co/npm/nodulator/)
14
15##### Under heavy development
16
17___
18## Concept
19
20`Nodulator` is designed to make it more easy to create highly modulable
21applications, built with REST APIs and with integrated ORM in CoffeeScript.
22
23You must understand [express](https://github.com/strongloop/express) basics for routing
24
25Open [exemples](https://github.com/Champii/Nodulator/blob/master/exemples)
26folder to see a full working exemple in JavaScript, CoffeeScript and LiveScript.
27
28Released under [GPLv2](https://github.com/Champii/Nodulator/blob/master/LICENSE.txt)
29
30### Following documentation is almost deprecated or incomplete and will be removed soon. Please prefer the most updated documentation at [http://nodulator.champii.io](http://nodulator.champii.io)
31
32___
33## Jump To
34
35- [Philosophy](#philosophy)
36- [Features](#features)
37- [Compatible modules](#compatible-modules)
38- [Installation](#installation)
39- [Quick Start (TL;DR)](#quick-start-tl;dr)
40- [Configuration](#configuration)
41- [Resource](#resource)
42 - [Basics](#basics)
43 - [Init](#init)
44 - [Class methods](#class-methods)
45 - [Instance methods](#instance-methods)
46 - [Promises or Callbacks](#promises-or-callbacks)
47 - [Schema](#schema)
48 - [Schema and Validation](#schema-and-validation)
49 - [Model association](#model-association)
50 - [LocalKey or DistantKey](#localkey-vs-distant-key)
51 - [Depth](#depth)
52 - [Overriding and Inheritance](#overriding-and-inheritance)
53 - [Override default behaviour](#override-default-behaviour)
54 - [Abstract Class](#abstract-class)
55 - [Complex inheritance system](#complex-inheritance-system)
56- [Route](#route)
57 - [Route Object](#route-object)
58 - [Request Object](#request-object)
59 - [Existing Routes](#existing-routes)
60 - [SingleRoute](#single-route-object)
61 - [MultiRoute](#multi-route-object)
62 - [Route Inheritance](#route-inheritance)
63 - [Standalone Routes](#standalone-routes)
64- [Wrappers](#route)
65 - [Flip Done](#flip-done)
66 - [Promise](#promise)
67 - [Watch Args](#watch-args)
68- [Reactive Values](#reactive-values)
69 - [Intro](#intro)
70 - [Resources](#resources)
71 - [Errors](#errors)
72- [DB Systems](#db-systems)
73 - [Abstraction](#abstraction)
74 - [Mysql](#mysql)
75 - [MongoDB](#mongodb)
76 - [SqlMem](#sqlmem)
77- [Other Stuff](#other-stuff)
78 - [Bus](#bus)
79- [Modules](#modules)
80 - [Usage](#usage)
81 - [Module Hacking](#module-hacking)
82- [Project Generation](#project-generation)
83- [Developers](#developers)
84- [Contributors](#contributors)
85- [TODO](#todo)
86- [Changelog](#changelog)
87
88___
89## Philosophy
90
91`Nodulator` is a project that is trying to make a big overlay to every
92traditional packages used to make REST client/server applications in CoffeeScript.
93
94Its main goal is to give developers a complex REST routing system, an ORM and
95high-level modules, encapsulating every classic behaviour needed to create
96complex projects.
97
98Its core provides everything needed to build powerfull and highly modulable
99REST APIs, and allow the developer to reuse his code through every projects.
100
101With this framework, you will never loose 10 or 20 hours anymore boostraping a
102project from scratch or looking for the right technology to implement.
103
104You will never have headache anymore trying to combine `socket.io` and `passport`
105 to keep track of your session with your sockets (for exemple),
106or you will never have to consider assets management,
107and with the integrated [Project Generation](#project-generation) you will never
108need to manage your `Nodulator` modules dependencies.
109
110You need to add authentication logic to your open/public API ? Look for
111[Nodulator-Account](https://github.com/Champii/Nodulator-Account) !
112
113You need to add socket.io support ? Look for
114[Nodulator-Socket](https://github.com/Champii/Nodulator-Socket) !
115
116If you don't find your desired module, just [build it](#modules) !
117
118`Nodulator` is like a lego game, instead of learning how to use a given
119technology and how to combine it with thoses you often use,
120it allows you to manipulate simple concepts like adding a `Account` concept to
121your application(for exemple), and so adding authentication and permission logic to your app.
122
123Also, each brick or layer of a `Nodulator` application is highly linked to every others.
124For exemple, when you add `Nodulator-Account` module to your app, if you have
125already included `Nodulator-Angular` it will automaticaly add everything needed
126to handle angular authentication (it will add a separate view, some directives
127and a user service). Have you added `Nodulator-Socket` ?
128So `Nodulator-Angular` will also be highly linked to your server's models,
129by providing a socket interface to your server `Resource`.
130
131Check the [Jump To](#jump-to) section !
132
133___
134## Features
135
136- [LiveScript](http://livescript.net/)
137- Integrated ORM
138- Integrated Routing system (with express, and highly linked with ORM)
139- Multiple DB Systems
140- Complex inheritance system
141- Chainable async calls
142- Modulable
143- Project generation
144- Cache
145- Schema-less/Schema-full models
146- Model validation
147- Model association (rails style) and automatic retrieval
148- Models and associations over different DB systems
149- Reactive values [Hacktiv](https://github.com/Champii/Hacktiv)
150- Promises or Callbacks
151- Fliped callback parameters
152- Log and Debug system
153- Console mode
154
155___
156### Compatible modules
157
158- [Nodulator-Assets](https://github.com/Champii/Nodulator-Assets):
159 - Automatic assets management
160- [Nodulator-Socket](https://github.com/Champii/Nodulator-Socket):
161 - Socket.io implementation for Nodulator
162- [Nodulator-Angular](https://github.com/Champii/Nodulator-Angular):
163 - Angular implementation for Nodulator
164 - Inheritance system
165 - Integrated and linked SocketIO
166 - Assets management
167- [Nodulator-Account](https://github.com/Champii/Nodulator-Account):
168 - Authentication with passport
169 - Permissions management
170 - Sessions
171 - Nodulator-Angular integration
172
173___
174## Installation
175
176Just run :
177```
178npm install nodulator
179```
180Or check the [Project Generation](#project-generation) section
181
182After you can require `Nodulator` as a module :
183
184```coffeescript
185N = require 'nodulator'
186```
187
188___
189## Quick Start TL;DR
190
191Here is the quickiest way to play around `Nodulator`
192
193```coffeescript
194_ = require 'underscore'
195N = require 'nodulator'
196
197class PlayerRoute extends N.Route.MultiRoute
198 Config: ->
199
200 # We create: GET => /api/1/{resource_name}/usernames
201 # Get a list of every players' usernames
202 # There is a @resource property, containing attached Resource class
203 @Get '/usernames' ~> @resource.ListUsernames!
204
205 # We call super() to apply N.Route.MultiRoute behaviour
206 # We called '/usernames' route before, so it won't be override by
207 # default route GET => /api/1/{resource_name}/:id
208 super!
209
210 # We create: PUT => /api/1/{resource_name}/:id/levelUp
211 @Put '/:id/levelUp' ~> it.instance.LevelUp!
212
213# We create a resource, and we attach the PlayerRoute
214class Players extends N 'player', PlayerRoute
215
216 # We create a LevelUp method
217 # If you dont provide done callback,
218 # the wrap promise will give one to fill and return as promise
219 LevelUp: @_WrapPromise (done) ->
220 @level++
221 @Save done
222
223 # And a class method to get a list of usernames
224 @ListUsernames: @_WrapPromise (done) ->
225 @List (err, players) ->
226 return done err if err?
227
228 done null, _(players).pluck 'username'
229
230
231```
232
233Go inside your project folder, copy this POC in a `test.coffee` file and type in:
234
235`$> coffee test.coffee`
236
237It will run your project on port `3000` by default
238
239Then open your favorite REST API Client ([Postman for Chrome](https://www.google.fr/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CCMQFjAA&url=https%3A%2F%2Fchrome.google.com%2Fwebstore%2Fdetail%2Fpostman-rest-client%2Ffdmmgilgnpjigdojojpjoooidkmcomcm%3Fhl%3Den&ei=Tu6iVMqpJZDaatmGgOAL&usg=AFQjCNHaecLwAKk91gpdCY_y1x_ViIrHwQ&sig2=3FcPD7i2Id8La26xJt4PJA&bvm=bv.82001339,d.d2s) is my favorite)
240
241and try the following routes :
242
243```
244(Assuming full url is always of the following form : "http://localhost:3000/api/1/[...]")
245Each route is of the following form :
246
247{VERB} {URL} ({PARAMS}) => {ANSWER}
248
249POST '/api/1/players' {username: 'test1', level: 1} => {id: 1, username: 'test1', level: 1}
250POST '/api/1/players' {username: 'test2', level: 1} => {id: 2, username: 'test2', level: 1}
251
252GET '/api/1/players' => [{id: 1, username: 'test1', level: 1},
253 {id: 2, username: 'test2', level: 1}]
254
255GET '/api/1/players/1' => {id: 1, username: 'test1', level: 1}
256GET '/api/1/players/2' => {id: 2, username: 'test2', level: 1}
257
258PUT '/api/1/players/2/levelUp' {} => {id: 2, username: 'test2', level: 2}
259PUT '/api/1/players/2/levelUp' {} => {id: 2, username: 'test2', level: 3}
260
261GET '/api/1/players/usernames' => ['test1', 'test2']
262
263PUT '/api/1/players/2' {username: 'notAUsername'} => {id: 2, username: 'notAUsername', level: 3}
264
265GET '/api/1/players/usernames' => ['test1', 'notAUsername']
266
267DELETE '/api/1/players/1' {} => {id: 1, username: 'test1', level: 1}
268
269GET '/api/1/players/usernames' => ['notAUsername']
270```
271
272___
273## Configuration
274
275First of all, the configuration process is absolutely optional.
276
277If you don't give Nodulator a config, it will assume you want to use
278[SqlMem](#sqlmem) DB system, with no persistance at all. Usefull for heavy tests periods.
279
280If you prefere to use a persistant system, here is the procedure :
281
282```coffeescript
283N = require 'nodulator'
284
285N.Config
286 dbType: 'Mongo' # You can select 'SqlMem' or 'Mongo' or 'Mysql'
287 dbAuth: # Fields needed if Mongo or Mysql
288 host: 'localhost'
289 database: 'test'
290 port: 27017 # From there, can be ignored. Default values taken
291 user: 'test' # |
292 pass: 'test' # |_
293```
294
295You can also provide a 'store' property in order to use `Redis` to manage sessions:
296
297```coffeescript
298N = require 'nodulator'
299
300N.Config
301 store:
302 type: 'redis'
303 host: 'localhost' # <- default value, can be ignored
304```
305
306If ommited, sessions will be memory based (not recommended)
307
308You can flip the arguments of the default `done` callback by specifying a `flipDone: true` parameter
309
310```coffeescript
311N.Config
312 flipDone: true
313```
314
315`Nodulator` provides 2 main Objects :
316
317```coffeescript
318N
319N.Route
320```
321
322___
323## Resource
324
325#### Basics
326
327A `Resource` is a class permitting to retrive and save a model from a DB.
328
329Here is an exemple of creating a `Resource`
330
331```coffeescript
332Players = N 'player'
333```
334
335Here, it creates a `PlayerResource`, linked with a `players` table in DB (if any)
336
337You can pass several params to `N` :
338
339```coffeescript
340N name [, Route] [, config]
341```
342
343You can attach a [Route](#route) and/or a config object to a `Resource`.
344
345
346#### Init
347
348This call prepare the `Resource`. All the `Nodulator`'s magic is
349inside this call.
350
351It is now optional to call Init(), as it will be called by the first Resource query.
352
353However, note that the Init() call can take the config parameter, in case of crossing
354Resource schema definition. You must call it befor the first query.
355
356Also, it returns the whole Resource for chaining purpose.
357
358#### Class methods
359
360Each `Resource` provides some 'Class methods' to manage the specific model in db :
361
362```coffeescript
363Players.Create(blob, done)
364Players.Fetch(constraints, done)
365Players.List(constraints, done)
366Players.Delete(constraints, done)
367```
368The `Create` method, like its name suggests, can add a row to a given Resource.
369
370```coffeescript
371Players.Create {login: 'player1', age: 24}, (err, player) ->
372 return console.error err if err?
373
374 [...] # Do something with player instance
375
376Players.Create [
377 {login: 'player1', age: 24}
378 {login: 'player2', age: 68}
379 {login: 'player3', age: 34}
380], (err, players) -> [...]
381```
382
383It automaticaly adds an `id` row to every new instance. Don't try to override it !
384
385The `Fetch` method can take an id and return a `Players` instance to `done` callback :
386It can take an id, an object or an array
387
388```coffeescript
389Players.Fetch 1, (err, player) ->
390 return console.error err if err?
391
392 [...] # Do something with player instance
393
394Players.Fetch [1, 5], (err, players) -> [...]
395Players.Fetch {login: 'value'}, (err, player) -> [...]
396Players.Fetch [7, {age: 25}, 9, {login: 'test'}], (err, players) -> [...]
397```
398
399The `Fetch` will return the first found row that pass the given object equality
400
401You can list every models from this `Resource` thanks to `List` call :
402
403```coffeescript
404Players.List (err, players) ->
405 return console.error err if err?
406
407 [...] # players is an array of every Players instance
408
409Players.List {age: 26}, (err, players) -> [...]
410Players.List [{age: 26}, {age: 52}], (err, players) -> [...]
411# Be carefull, as List(obj) returns an array, List(array) returns an Array of Array
412```
413
414You can delete the same way, except that Delete callback only have one error parameter.
415
416Be carefull, The `Delete` will erase the first found row that pass the given object equality
417
418```coffeescript
419Players.Delete 1, (err) ->
420 return console.error err if err?
421
422
423Players.Delete {login: 'toto'}, (err) -> [...]
424Players.Delete [9, {login: 'toto'}, 4, {age: 22}], (err) -> [...]
425```
426
427Never use `new` operator directly on a `Resource`, else you might bypass the
428relationning system.
429
430#### Instance methods
431
432A player instance has some methods :
433
434```
435player.Save(done)
436 Save the model in DB. The callback take 2 arguments : (err, instance) ->
437
438player.Delete(done)
439 Delete the model from the DB. The callback take 1 argument : (err) ->
440
441player.Serialize()
442 Get every object properties, and return it in a new object.
443 Generaly used to get what to be saved in DB.
444
445player.ToJSON()
446 By default, it calls Serialize().
447 Generaly used to get what to send to client.
448
449player.ExtendSafe(blob)
450 This method extend the Resource with the given blob,
451 But it keeps safe every Associations and vital stuff.
452```
453
454Every Instance and Class methods can be chained, they all returns their 'this'. (Except when using Promises,
455read the corresponding chapter)
456
457___
458## Promises or Callbacks
459
460Every call that take a `done` callback can be expressed by Promises:
461
462```coffeescript
463Players.Fetch 1, (err, player) ->
464 return console.error err if err?
465
466 [...] # Do something with player instance
467```
468
469Is equivalent to :
470
471```coffeescript
472Players.Fetch 1
473 .then (player) -> [...] # Do something with player instance
474 .fail (err) -> console.error err
475
476```
477
478If you omit the callback, it will return a Promise. If you pass a callback, it will return `this` for chaining purpose.
479
480___
481## Schema
482
483#### Schema and Validation
484
485By default, every `Resource` is schema less. It means that you can put almost
486anything into your `Resource`.
487
488It can obviously be schema less only for DB systems that allows it. When using
489MySQL for exemple, you'll have to define a schema and validation rules
490if you don't want your server to answer raw SQL errors for non existant fields
491
492To make a `Resource` to respect a given schema, you just have to define a
493`schema` field into `Resource` configuration
494
495```coffeescript
496config =
497 schema:
498 toto: 'array' #This can be an array of everything
499 test: ['int'] #This MUST be an array of integer
500 foo: 'int'
501 bar:
502 type: 'string'
503 optional: true
504 foobar:
505 type: 'string'
506 default: 'foobar'
507```
508
509Differents types are
510- bool
511- int
512- string
513- date
514- email
515- array
516
517By default, each fields is required, but you can make one field optional with
518the `optional` field to `true` or presence of `default` field. It will never
519complain if this field is not present, but if it is, it will check for its validity.
520
521
522You can specify a type directly with a string, assuming that the given property
523will be required:
524
525```coffeescript
526config =
527 schema:
528 foo: 'int'
529 bar: 'string'
530```
531
532#### Default and Virtual fields
533
534If you specify a `default` field, the `Resource` will auto-set its property
535if the value is not given.
536
537For exemple, with this Resource config :
538
539```coffeescript
540config =
541 schema:
542 foo:
543 type: 'int'
544 default: 5
545```
546
547If I dont provide a 'foo' value at Resource instanciation, it will take the
548one given by the 'default' field
549
550You can put a function in place of a default value. In this case, this function
551will be executed to get the default value at instanciation time.
552
553```coffeescript
554config =
555 schema:
556 foo:
557 type: 'int'
558 default: (obj) ->
559```
560
561
562#### Model association
563
564You can make associations between `Resource`. For making a `Resource` to be
565automaticaly fetched when querying another, you can add it to its schema :
566
567```coffeescript
568
569Bars = N 'bar', {schema: barProperty: 'string'}
570
571config =
572 schema:
573 foo: 'int'
574 barId: 'int'
575 bar:
576 type: Bars
577 localKey: 'barId'
578
579Tests = N 'test', config
580
581# Fetch Tests with id == 1
582Tests.Fetch 1, (err, test) ->
583 # Will be for exemple :
584 # {
585 # id: 1,
586 # foo: 12,
587 # barId: 1,
588 # bar: {id: 1, barProperty: 'test'}
589 # }
590
591```
592
593If you want to retrive a collection of resource, you can wrap types in arrays instead:
594
595```coffeescript
596config =
597 schema:
598 foo: 'int'
599 barIds: ['int']
600 bar:
601 type: [Bars]
602 localKey: 'barIds'
603
604Tests = N 'test', config
605
606# Fetch Tests with id == 1
607Tests.Fetch 1, (err, test) ->
608 # Will be for exemple :
609 # {
610 # id: 1,
611 # foo: 12,
612 # barIds: [1, 2],
613 # bar: [{id: 1, barProperty: 'test'},
614 # {id: 2, barProperty: 'test2'}]
615 # }
616
617```
618
619#### localKey vs distantKey
620
621When you want to retrieve a Resource based on an id property inside the object,
622you must use the 'localKey' property. It will fetch for the given Resource Type
623with the corresponding id from 'localKey' field
624
625If can also retrieve a Resource that have a property containing the actual instance id:
626
627```coffeescript
628Bars = N 'bar', {schema: testId: 'int'}
629
630config =
631 schema:
632 foo: 'int'
633 bar:
634 type: Bars
635 distantKey: 'testId'
636
637Tests = N 'test', config
638
639Tests.Create {foo: 12}
640.then -> Bars.Create {testId: 1} #We assume its the first created resource
641.then -> Tests.Fetch 1 #We know its the first resource created
642.then (test) -> #Here test have a 'bar' property with testId == 1
643
644```
645
646Given the last exemple, when you get a Test instance with id == 1, it will have a
647field 'bar' with the Bars instance that have testId == 1
648
649#### Depth
650
651When you have associated Resources, you can choose the depth of your queries.
652
653By default, the depth is at 1 for every Resource. It means that it will fetch only
654one level of Resource maximum.
655
656If you want a deeper level of retrieving, you can set the 'maxDepth' property of
657a Resource configuration:
658
659```coffeescript
660config =
661 maxDepth: 3
662```
663
664Or if you prefer change it at runtime and make it inheritable, you can act on
665the Resource.DEFAULT_DEPTH field.
666
667Note that the config one has priority.
668
669____
670## Overriding and Inheritance
671
672You can inherit from a `Resource` to override or enhance its default behaviour,
673or to make a complex class inheritance system built on `Resource`
674
675#### Override default behaviour
676In CoffeeScript its pretty easy:
677
678```coffeescript
679class Units extends N 'unit'
680
681 # We create a new instance method
682 LevelUp: (done) ->
683 @level++
684 @Save done
685
686 # We override default 'List' method
687 @List: (done) ->
688 @ListBy {life: 10}, (err, units) ->
689 return done err if err?
690
691 done null, units
692
693```
694
695#### Abstract class
696
697You can define an abstract class, that won't be attached to any model in DB or
698any `Route`
699
700```coffeescript
701class Units extends N 'unit', {abstract: true}
702 [...]
703
704```
705
706Of course, abstract classes are only designed to be inherited. (Please note that
707 they can't have a `Route` attached)
708
709#### Complex inheritance system
710
711Given the last exemple, here is a class that inherits from `Units`
712
713```coffeescript
714# Note the call to 'Extend()' method
715class Players extends Units.Extend 'player'
716
717 # Give Players a new beheviour
718 NewBehaviour: (args, done) ->
719 [...]
720
721 # Overriding existing Units LevelUp()
722 LevelUp: (done) ->
723 [...]
724
725```
726
727You can call the Extend() method either from a full `Resource` or from an
728`abstract` one.
729
730Please note that if both parent and child are full `Resource`, both will have
731corresponding model available from ORM (here `units` and `players`)
732
733So be carefull when creating extended `Resource`, and think about `abstract` !
734
735___
736## Route
737
738#### Route Object
739
740`Nodulator` provides a `Route` object, which can to be attached to a `Resource` object (or not)
741in order to describe routing process.
742
743There is 2 different ways to attach a `Resource` to a `Route`:
744
745You can bind a `Route` to a `Resource`
746
747```coffeescript
748class Units extends N 'unit', N.Route
749```
750
751Or you can bind a `Resource` to a `Route`
752
753```coffeescript
754class UnitRoute extends N.Route
755 resource: Units
756
757#In this case, you have to instanciate the Route yourself once
758route = new UnitRoute
759```
760
761Every `Route` can be initiated and configured when its attached `Resource` is,
762else you must instantiate one yourself.
763
764Default `N.Route` do nothing. You have to inherit from it to describe routes :
765
766```coffeescript
767class UnitRoute extends N.Route
768
769 # Override the Config() method
770 Config: ->
771
772 # And never forget to call the super()
773 super()
774
775 # Here we define: GET => /api/1/{resource_name}/:id
776 @Get '/:id', (req, res) =>
777
778 # The @resource field points to attached Resource
779 @resource.Fetch req.params.id, (err, unit) ->
780 return res.status(500).send err if err?
781
782 res.status(200).send unit.ToJSON()
783
784 # Here we define: POST => /api/1/{resource_name}
785 @Post (req, res) ->
786 res.status(200).end()
787```
788
789This `Route`, attached to a `Resource` (here `Units`) add 2 endpoints :
790
791```
792GET => /api/1/units/:id
793POST => /api/1/units
794```
795
796Each `Route` have to implement a `Config()` method, calling `super()` and
797defining routes thanks to 'verbs' route calls (@Get(), @Post(), @Put(), @Delete(), @All()).
798
799Here are all 'verb' route calls definition :
800
801```coffeescript
802N.Route.All [endPoint = '/'], [middleware, [middleware, ...]], callback
803N.Route.Get [endPoint = '/'], [middleware, [middleware, ...]], callback
804N.Route.Post [endPoint = '/'], [middleware, [middleware, ...]], callback
805N.Route.Put [endPoint = '/'], [middleware, [middleware, ...]], callback
806N.Route.Delete [endPoint = '/'], [middleware, [middleware, ...]], callback
807```
808
809#### Single Route Object
810
811Nodulator provides a predefined route system for lazy, adapted for Singleton
812`Resource`: `N.Route.SingleRoute`.
813
814It setups 2 routes (exemple when attached to a `Players`) :
815
816```
817GET => /api/1/player => Fetch
818PUT => /api/1/player => Update
819```
820
821This route system needs to have a resource with `id == 1` in your actual DB
822before startup time to work.
823
824If you don't have a `config.schema` property set in your `Resource`, it will
825create one for you at startup time.
826
827Else, `Nodulator` will throw an error and shutdown.
828
829If you use `SqlMem` DB system, you must add a 'default' value to each resource
830fields in order to add it at startup.
831
832
833#### Multi Route Object
834
835Nodulator provides also a standard route system for lazy : `N.Route.MultiRoute`.
836It allows you to handle your resources like its a big collection.
837 It setups 5 routes (exemple when attached to a `Players`) :
838
839```
840GET => /api/1/players => List
841POST => /api/1/players => Create
842GET => /api/1/players/:id => Get One
843PUT => /api/1/players/:id => Update
844DELETE => /api/1/players/:id => Delete
845```
846
847Note that the first GET call accept query parameters for selection.
848
849#### Route Inheritance
850
851You can inherit from any route object :
852
853```coffeescript
854class Test1Route extends N.Route
855class Test2Route extends N.Route.MultiRoute
856class Test3Route extends Test2Route
857class Test4Route extends Test3Route
858```
859And you can override existing route by providing same association verb + url.
860Exemple :
861
862```coffeescript
863class TestRoute extends N.Route.MultiRoute
864 Config: ->
865 super()
866
867 # Here we override the default GET => /api/1/{resource_name}/:id
868 @Get '/:id', (req, res) =>
869 [...]
870```
871
872#### Standalone Routes
873
874You can declare Route that dont belongs to any Resource.
875Instead, you define yourself every endpoints :
876
877```coffeescript
878class TestRoute extends N.Route
879
880 Config: ->
881 super()
882
883 @Get (req, res) =>
884 res.status(200).send {elem: 'value'}
885
886# You have to give them a name, to replace the one a Resource would have given
887route = new TestRoute 'test'
888```
889
890Create the following routes :
891
892```
893GET => /api/1/tests
894```
895___
896## Reactive Values
897
898 See [Hacktiv](https://github.com/Champii/Hacktiv) documentation for more informations
899
900#### Intro
901
902Reactive programing benefits don't have to be proven. It just works.
903
904You can watch for a fonction and it will be re-executed when reactive values
905inside have changed.
906
907The following exemple will print successively 1 and 2
908
909```coffeescript
910val = new N.Watch.Value 1
911
912# The function is executed a first time
913N.Watch ->
914 console.log val()
915
916# This call will cause the function to be reexecuted.
917val 2
918```
919
920#### Resource
921
922You just saw that Nodulator provide the default Hacktiv library.
923
924You can also watch for data-sets to change to retrigger the function :
925
926```coffeescript
927Tests = N 'test'
928
929[...]
930
931N.Watch ->
932 Tests.Fetch 1, (err, test) ->
933
934Tests.Fetch 1
935.then (test) ->
936 test.prop = 'value'
937
938 # This call will cause the watching function to be reexecuted.
939 test.Save()
940```
941
942It works also with List:
943
944```coffeescript
945N.Watch ->
946 # Get a double array
947 Tests.List [
948 {prop1: 'value1'}
949 {prop1: 'value2'}
950 ], (err, test) ->
951 [...]
952
953
954Tests.Fetch {prop1: 'value1'}
955.then (test) ->
956 test.prop = 'value3'
957
958 # This call will cause the watching function to be reexecuted.
959 test.Save()
960```
961
962
963#### Errors
964
965Every Resource have his own reactive Error property that can be watched:
966
967```coffeescript
968N.Watch ->
969 console.error Tests.error()
970
971# will produce an error and refresh the console.log above
972Tests.Fetch {unknownProp: 'val'}, (err, test) ->
973```
974
975Very usefull with `flipDone: true` option.
976
977___
978## Db Systems
979
980#### Abstraction
981
982We defined a driver interface for some DB implementations.
983
984It's based on SQL `Table` concept. (see [lib/connectors/sql/index.coffee](https://github.com/Champii/Nodulator/blob/master/lib/connectors/sql/index.coffee))
985
986```coffeescript
987Table.Find(id, done)
988Table.FindWhere(fields, where, done)
989Table.Select(fields, where, options, done)
990Table.Save(blob, done)
991Table.Insert(blob, done)
992Table.Update(blob, where, done)
993Table.Delete(id, done)
994```
995
996Every `Resource` have an associated `Table` instance that links to the good
997table/document in the good DB driver system
998
999#### Mysql
1000
1001Built-in `MySQL` implementation ([node-mysql](https://github.com/felixge/node-mysql/))
1002for `Nodulator`
1003
1004Check [lib/connectors/sql/Mysql.coffee](https://github.com/Champii/Nodulator/blob/master/lib/connectors/sql/Mysql.coffee)
1005
1006#### MongoDB
1007
1008Built-in `MongoDB` implementation ([mongous](https://github.com/amark/mongous))
1009for `Nodulator`
1010
1011Check [lib/connectors/sql/Mongo.coffee](https://github.com/Champii/Nodulator/blob/master/lib/connectors/sql/Mongo.coffee)
1012
1013#### SqlMem
1014
1015Special DB driver, built on RAM.
1016
1017It provides same options as others systems do, but nothing is stored. When you
1018stop the server, everything is deleted.
1019
1020Check [lib/connectors/sql/SqlMem.coffee](https://github.com/Champii/Nodulator/blob/master/lib/connectors/sql/SqlMem.coffee)
1021
1022___
1023## Other stuff
1024
1025#### Bus
1026
1027There is a `N.bus` object that is basicaly an `EventEmitter`. Every
1028objects in `Nodulator` use this bus.
1029
1030Here are the emitted events:
1031
1032- On a new `Resource` being inserted in DB, sends it after a `Serialize()` call
1033 - `N.bus.emit 'new_' + resource_name, @Serialize()`
1034
1035- On a `Resource` being updated in DB, sends it after a `Serialize()` call
1036 - `N.bus.emit 'update_' + resource_name, @Serialize()`
1037
1038- On a `Resource` being deleted from DB, sends it after a `Serialize()` call
1039 - `N.bus.emit 'delete_' + resource_name, @Serialize()`
1040
1041Exemple
1042
1043```coffeescript
1044Players = N 'player'
1045
1046N.on 'new_player', (player) ->
1047 [...] # Do something with this brand new player
1048```
1049
1050You can override default `Bus` by setting new class to N.Bus :
1051
1052```coffeescript
1053N = require 'nodulator'
1054NewBus = require './NewBus'
1055
1056N.Bus = NewBus
1057```
1058
1059Always set new `Bus` before any new `Resource` call or any added `Module`
1060
1061___
1062## Modules
1063
1064#### Usage
1065
1066To inject a module into `Nodulator`, preceed this way :
1067
1068```coffeescript
1069N = require 'nodulator'
1070ModuleName = require 'nodulator-ModuleName'
1071
1072N.Use ModuleName
1073```
1074
1075Replace `ModuleName` with the module's name you want to load
1076
1077#### Module Hacking
1078
1079If you want to create a new module for `Nodulator`, you have to export a single
1080function, taking `Nodulator` as parameter :
1081
1082```coffeescript
1083module.exports = (Nodulator) ->
1084 [...] # Your module here
1085```
1086
1087You can extend anything you want, as the whole `Nodulator` object is passed to your function.
1088
1089Be carefull to `server/loadOrder.json`.
1090
1091Watch how [other modules](#compatible-modules-and-dependencies) are made !
1092
1093___
1094## Project Generation
1095You can get global `Nodulator` :
1096
1097```
1098$> npm install -g nodulator
1099$> Nodulator
1100Usage: Nodulator (init) | ((install | install-dev | remove) moduleName1 [, moduleName2 [, ...]])
1101```
1102
1103Nodulator provides a way of installing `Nodulator`, modules and dependencies easely
1104```
1105# If no arguments, install or remove Nodulator
1106$> Nodulator install
1107$> Nodulator install-dev
1108$> Nodulator remove
1109
1110# Will install nodulator-angular and every dependencies (if any)
1111$> Nodulator install angular
1112
1113# Will install nodulator-angular, nodulator-account, and all their dependencies (if any)
1114$> Nodulator install angular account
1115
1116# Will create local link instead of a full install of nodulator-angular and every dependencies (if any)
1117# It's used to avoid reinstalling locally a Nodulator package under development
1118$> Nodulator install-dev angular
1119
1120# Will remove nodulator-socket
1121$> Nodulator remove socket
1122```
1123
1124Then you can launch the `init` process :
1125```
1126$> Nodulator init
1127```
1128
1129It creates the following structure if non-existant :
1130```
1131main.coffee
1132package.json
1133settings/
1134server/
1135├── index.coffee
1136├── loadOrder.json
1137├── processors/
1138│   └── index.coffee
1139└── resources/
1140    └── index.coffee
1141```
1142
1143And then find for every `Nodulator` modules installed, and call their
1144respective `init` method.
1145
1146It generate a `main.coffee` and a `package.json` with every modules pre-loaded.
1147
1148The `server` folder is auto-loaded (check `server/index.coffee` and every
1149 `index.coffee` in subfolders).
1150
1151Folders load order is defined in `server/loadOrder.json`, and is automaticaly
1152managed by new modules installed (they care of the order)
1153
1154You can immediately start to write `Resource` in `server/resources` !
1155
1156___
1157## Developers
1158
1159Never forget that I'm always available at contact@champii.io for any questions
1160 (Job related or not ;-)
1161
1162___
1163## Contributors
1164
1165- [Champii](https://github.com/champii)
1166- [SkinyMonkey](https://github.com/skinymonkey)
1167
1168___
1169## ToDo
1170
1171By order of priority
1172
1173- Paginated Resource
1174- When cache expire, remove correspondant Watcher /!\
1175- Inherit associations (care for abstract Resources)
1176- Remove an existing route easely
1177- Better query on Resource (gt, gte, lt, lte, not, range, ...)
1178- Migration system
1179- Auto wrap new methods in `Resource`
1180- Association Polymorphism
1181- Watch a specific field
1182- Relations not only based on id but on every property types
1183- Persistant sessions in Console
1184- 'Unique' field
1185- 0bject OwnRoute that perform from logged user (/api/1/player or /api/1/tasks for exemple)
1186- Decentralize modules config
1187- Scaling (cluster, distributed bus)
1188- Split Resource into smaller Classes
1189- List return a resource that can act on each item (Set, Add, ...) ( Extend Array ? )
1190- Better tests
1191 - Request
1192 - Multi Driver fetch/list
1193 - Db
1194 - SqlMem
1195 - Mysql
1196 - Mongo
1197 - Ids
1198 - HABTM tables
1199 - Cache
1200 - Config oveloading
1201 - Schema
1202 - HasOne
1203 - HasMany
1204 - HasOneThrough
1205 - HasManyThrough
1206 - HasAndBelongsToMany
1207
1208
1209___
1210## ChangeLog
1211XX/XX/XX: current (not released yet)
1212 -
1213
121428/11/15: 0.1.0
1215 - LiveScript ! \o/
1216 - Lib Folder reorganisation
1217 - Added benchmarks folder
1218 - Replaced @instance in Route by req._instance.
1219 - Added a Request class to handle Resource in Route
1220 - The Route class can take a Resource as property. Also, Routes can be Instanciated.
1221 - Changed every 'Nodulator' call by 'N', more readable
1222 - Added a debug system with 'debug'
1223 - Console mode to "connect" to an existing/running Nodulator instance and perform standard calls
1224 - Joined Every modules into this git repo for a stronger compatibility
1225 - Shortcut the N.Resource() into a simple N()
1226 - Added a 'Watch' for both Instance and Class of Resource, that allow more flexibility
1227 - Added a virtual field filled when the resource is.
1228 - Added HasOne, HasMany, BelongsTo and BelongsToMany calls. Buggy at the moment and not standard
1229 - Added _WrapDebug() Wrapper
1230 - Web server now starts only when needed (when first Route is being declared)
1231 - Fixed validation fails
1232 - Added _CreateUnwrapped
1233 - Added a instance.Watch() call, to make the instance to auto-update when part of it change
1234 - Schema 'strict' or 'free'
1235 - When in Association, if the localKey doesnt exists, it is created on the fly
1236 - Better HasOne, HasMany and BelongsTo.
1237 - HasOneThrough, HasManyThrough
1238 - HasAndBelongsToMany
1239 - Resources can override global db config to put different models on different db systems
1240 - Resource instance can be created on different db than default
1241 - Added Cache over Redis
1242 - Added @Hydrate() function to populate properties and associations from cache
1243 - Added configuration for cache
1244 - Better configuration for db
1245 - Internal driver is now fixed to be the default Nodulator driver.
1246 - Globalized ids management by external table
1247 - internal_ids are now stored on default driver and are automaticaly updated with last Ids values
1248 - Chainable calls !
1249 - Create can now take a promise instead of an id
1250 - Better Remove() for MayHas*() associations
1251 - Schema is now inherited by copy
1252 - Internal() Field property that is not put in the JSON produced by ToJSON(), so not sent to any client but saved to DB anyway
1253 - JSON and object validation type
1254 - Each resource is available through N.Resourcename (exemple for 'player' : N.Player)
1255 - When a route is attached to a Resource, it is now available as Resource.Route
1256
125721/07/15: v0.0.19
1258 - Added `SingleRoute` object, for manipulating Singleton `Resource`
1259 - Removed `req.instances` from every `Route`
1260 - Added tests for `SingleRoute`
1261 - Route proxy methods for `@_All()` are now generated at runtime
1262 - Renamed `DefaultRoute` to `MultiRoute`
1263 - Added a `default` field to config schema
1264 - `Resource.Init()` now returns the `Resource` itself, for chaining purpose.
1265 - Added tests for resource association
1266 - Tests are now executed in specific order
1267 - You can now give an array as schema type for a field, in order to retrive
1268 multiple resources based on id
1269 - Added Javascript support
1270 - Added an output line to tell the user when the framework is listening and
1271 to which port
1272 - Fetch and Create can now take one argument or an array of arguments
1273 - Fixed bugs on resource association:
1274 - ToJSON() now call child ToJSON() instead of Serialize()
1275 - ToJSON() call check if given association exists
1276 - Added 'distantKey' in relational schema to fetch relation that have that
1277 resource id as given key
1278 - Added maxDepth field to resource config in order to limit the relationnal
1279 fetch. There is also a Resource.DEFAULT_DEPTH constant that is used when nothing is precised.
1280 - Added argument to Resource.Init(): You can give the config object in order
1281 to avoid recursive require when two way model association
1282 - Removed Doc section. It will be on the website documentation.
1283 - Code in Init() has been splited for code clarity
1284 - Load order has changed between resources and socket
1285 - Added a @_type variable defining the typename of an instance
1286 - Fixed a bug in model association: no field in schema if array with no 'type'
1287 - Improved 'arrayOf' type check
1288 - Added function to default schema value (is that a possible virtual field ?)
1289 - MultiRoute::Get() now can take query arguments
1290 - Added Resource::ExtendSafe() method to preserve associated models while extending a Resource
1291 - Modified Route::MultiRoute and Route::SingleRoute to use Resource::ExtendSafe()
1292 - Removed app parameter from Route constructor
1293 - Route classes can now be instanciated without any Resources
1294 - Removed ListBy and FetchBy for simplicity
1295 - Resource::Deserialize() is now a private call : Resource::_Deserialize()
1296 - Removed the mandatory Init function call !
1297 - Added Promises if no callback given.
1298 - Routes are now instantiated when attached, not when Init. This helps the new lazy Init system
1299 - List can now take an array
1300 - Added Hacktiv support for Resources
1301 - Added a ChangeWatcher for Resources that watch for the result of a query to make change
1302 - You can now add a `flipDone: true` to the N.Config() call to have callback like (data, err) ->
1303 - Added Wrappers class to regroup every wrappers.
1304 - Added Wrappers for Promises, FlipDone, and for WatchArgs
1305 - Extend now dont need to be abstract to work
1306 - Added tests for promisesm FlipDone and reactive watching
1307
130804/05/15: v0.0.18
1309 - You can specify a 'store' general config property in order to switch to redis-based sessions
1310
131103/05/15: v0.0.17
1312 - You can now specify a property type in schema without wrapping it in a object like {type: "string"}
1313
131415/04/15: v0.0.16
1315 - Removed redis references for sessions
1316
131714/04/15: v0.0.15
1318 - Minor changes in `Route` to fit [Nodulator-Account](https://github.com/Champii/Nodulator-Account) new release
1319
132010/04/15: v0.0.14
1321 - Resource 'user' is no longer a reserved word
1322 - Resources with name finishing with 'y' are now correctly put in plural form in route name
1323
132409/04/15: v0.0.13
1325 - Better model association and validation
1326 - Pre-fetched resources in `Route.All()` are now put in `@instance` instead of `req[@resource.lname]`
1327 - Updated README
1328 - Updated Mongo driver
1329
133020/01/15: v0.0.12
1331 - Fixed bug on FetchBy
1332
133320/01/15: v0.0.11
1334 - Fixed tests
1335 - Added travis support for tests
1336 - Added model associations
1337 - Added schema and model validation
1338 - Changed `FetchBy` and `ListBy` prototype. Now take an object instead of a key/value pair.
1339 - Added `Create()` method into `Resource`
1340 - Added `limit` and `offset` to both Mysql and SqlMem
1341
134207/01/15: v0.0.10
1343 - Added Philosophy section
1344 - Added multiple package name support in package generation
1345 - Fixed some bugs with modules
1346
134703/01/15: v0.0.9
1348 - Separated `AccountResource` into a new module [Nodulator-Account](https://github.com/Champii/Nodulator-Account)
1349 - Changed README
1350
135102/01/15: v0.0.8
1352 - Fixed Route middleware issue
1353
135402/01/15: v0.0.7
1355 - Separated `Socket` into a new module [Nodulator-Socket](https://github.com/Champii/Nodulator-Socket)
1356 - Added new methods for `@Get()`, `@Post()`, `@Delete()`, `@Put()`, `@All()` in `Route`
1357 - Replace old method `@All()` into `@_All()`. Is now a private call.
1358 - Improved README (added [Modules](#modules) section)
1359 - Global `Nodulator` now manage dependencies installation