ActiveRecord implementation for ES2015 with neo4j as back-end
this software is beta stage and is not intended to be used in serous production projects developers of this software are not responsible for data loss and corruption, lunar eclipses and dead kittens
What is it?
ActiveRecord is common pattern in software development which declares that there is special class or classes who are responsible for database reflection, line-by-line or node-by-node.
Neo4j is Graph Database, it's schema-less and ACID-compliant.
How to use it?
Dead simple. It's mostly purposed for ES2015-featured JavaScript, so all of the examples are written using it.
const Connection Record = {}Entryconnection = 'http://neo4j:password@localhost:7474'; // or with babel-preset-stage-1// here and further where static properties are used they can be replaced// by assignment of property to class function, like shown above static connection = 'http://neo4j:password@localhost:7474'; Entry //creates indexes and makes some internal magic for resolving { const entry = entryfoo = 'bar' await entry const entries = await Entry console // => 1 console // => 'bar'}
Wait, but I need relations
no problems. It's dead simple too:
const Connection Record Relation = static connection = 'http://neo4j:password@localhost:7474'; subjects = this 'relation' /*internal relation label-name*/; //note: it is NOT a static property. It can be replaced with { super...args; thissubjects = this 'relation' /*internal relation label-name*/; } //target is optional! and direction is optional too, it should be -1 for reverse relations. subjects = this 'relation' target: Object direction: -1; RecordObjectRecordSubject { const object = await baz: true const subject = await await objectsubjects console // => 1 const objects = await subjectobjects console //true}
even for deep relations:
roles = this 'has_role' target: Role; permissions = thisroles 'has_permission' target: Permission; async { return await thispermissions } users = this 'has_role' target: Role direction: -1; permissions = this 'has_permission' target: Permission; roles = this 'has_permission' target: Role direction: -1; users = thisroles 'has_role' target: User direction: -1;
Relation instances have bunch of pretty methods to use (you can always pass a transaction as last argument):
async : Record async : void async : bool async : Array<Record> async : void async deleterecords: Array<Record>: void async : void async : number async : Array<Record> async : Array<Record>
AGR-backed fields
AGR automatically brings uuid key (cannot be re-defined), created_at and updated_at (in milliseconds) fields when record is reflected.
OK, but how can I make complex queries?
Record and Relation have static where method to use for querying. All details are provided in API page, in brief - order, limit, offset can be used for filtering, equality, existence, numeric (greater/less), string (starts/ends with, contains), array (contains/includes) queries are available
Examples:
EntryEntryEntry // here e.g. {foo: [1,2,3,4,5], bar: 3} will be reflected.// $has stands for "db record has fields", $in - for "db record is in list of possible fields"Entry //$in can also work with arrayEntry
Hooks?
Sure. beforeCreate, afterCreate, beforeUpdate, afterUpdate, beforeDestroy, afterDestroy are available hooks
async { //this.connection points here to transaction, so you have to pass it if calling other classes const test = await Test thistestId = testtestId }
Transactions and atomicity?
Yes, AGR has transactions. Hooks (see above) are always inside a transaction. They can be used by using special decorator (with babel-plugin-transform-decorators-legacy, will be changed to new syntax when new spec will become stable) @acceptsTransaction({force: true}) or called explicitly by connection.transaction() All transactions should be committed or rolled back. On SIGINT AGR will attempt to rollback all not closed yet transactions. By default Neo4j rolls back transactions in 60 seconds after last query.
Good example of transaction usage is Record#firstOrCreate sugar-ish method:
@acceptsTransaction static async { const tx = thisconnection let result = await this if !result result = await await tx return result }
Roadmap
- sort
- offset and limit
- has
- deep relations
- indexes
- rich Record.where query syntax ($lt, $gt, $lte, $gte, $has, $in and so on)
- one-to-one relations
- relations validation
- total test coverage
- performance optimisations
- optimistic and pessimistic locks?
- tests for transaction usage
- tests for eventEmitter