-
Notifications
You must be signed in to change notification settings - Fork 0
persistent, code reloading, interactive object space for node
License
natecain/odot
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
DESCRIPTION odot is a persistent, interactive object space with code reloading. It is based on Node. EXAMPLE Three files (in example/ directory): trainset.js - Defines train set with trains and stations train.js - Default values and functions for trains station.js - Default values and functions for stations First session: $ node example/trainset.js > alpha = o.Station('alpha') > beta = o.Station('beta') > beta.mile = 60 // Override default of 0 set in station.js > regular = o.Train('regular') > console.log(regular.speed) => 10 // Is default specified in train.js > express = o.Train('express') > express.speed = 20 > regular.station = alpha // train.js specifies one train->station relation > express.station = alpha > express.travel(beta) => "express now at beta after 3 hours travel" > // Make change to train.js to change travel() function output > recode() // Dynamically reloads code from train.js and station.js > regular.travel(beta) => "regular goes to beta, travel time 6 hours" > <Ctrl-C> // Automatic persistence on exit to o.json file in working directory Second session: $ node example/trainset.js > // Automatic load of database from o.json file in working directory > beta = o.station.beta // or o.station['beta'] > console.log(beta.distance) => 60 // Data is preserved > console.log(beta.train[0].id) => 'express' // Many relation station->train > regular = o.train.regular > console.log(regular.station.id) => 'beta' // One relation train->station > regular.speed = 15 > reset() // Resets entire database back to last save point or state at load > regular = o.train.regular // Remember to re-reference to get reset state > console.log(regular.speed) => 10 > regular.speed = 15 > save() // Persists current state of database (like a commit) > reset() // Resets to the last save point, which was after speed set to 15 > regular = o.train.regular > console.log(regular.speed) => 15 > <Ctrl-C> Third session: $ node example/trainset.js > console.log(o.train.regular.speed) => 15 > <Ctrl-C> INSTALLATION If you use npm: npm install odot If you do not use npm, clone this repository or download the latest version using the GitHub repository Downloads link. DB SCRIPT You must write a script to define your database. You launch your interactive session by executing this script with Node. First, import the odot module: var odot = require('odot') map = odot.map; If you did not install via npm, require your checked out or extracted odot directory instead of 'odot'. For each collection in your database, provide the path of a prototype script: map('train', 'train.js'); map('station', 'station.js'); Finally, load the db and enter the interactive repl prompt (see WITHOUT PROMPT for how to use the database without a repl prompt). odot.repl() PROTOTYPE SCRIPTS Prototype scripts define the default variables and functions of prototypes. Prototypes undergird all the objects of particular collections in your database. A very basic example (example/station.js): var mile = 0; // Set default value of on station prototype many('train'); // Create a many relation with trains function name() { // Custom function for printing out station name return 'station ' + this.id + ' at ' + this.mile; } All variables and functions defined become those of the collection prototype. Special function calls allow you to setup relations with objects of other collections in the database (see RELATIONS below). Prototype scripts are dynamic. You can continue editing them while the node process and/or prompt is up, then, when you want the latest code loaded into your node process, use the recode() (or rc(), alias to same) method. This will load the latest code from all your prototype scripts into the prototypes undergirding the objects in your database, giving the objects new defaults and behaviors without restarting the process. RELATIONS You can manually add functions to your prototype scripts to reference objects of other collections in your database. However, since this is a common pattern, prototype setup methods are provided to let you define 'one' or 'many' relations. For example, in train.js: one('station') Gives all train objects a station member that is transparently set or accessed by: > regular = o.Train('regular') > alpha = o.Station('alpha') > regular.station = alpha > regular.station // => alpha You can also define a relation to one or more objects of another collection. For example, in station.js: many('train') Gives all station objects a train member that is an array of train objects: > regular = o.Train('regular') > express = o.Train('express') > alpha = o.Station('alpha') > alpha.train = [regular, express] > alpha.train // => [regular, express] The array returned is a real JavaScript array that is created at the time of call, therefore you can use the full set of array methods on it, but anything you do to the array does not affect the actual relations held by the object. To add or remove trains individually from the station's many relation: > slow = o.Train('slow'); > alpha.add(slow) > alpha.train // => [regular, express, slow] > alpha.remove(express) > alpha.train // => [regular, slow] Relationships are not automatically managed bi-directionally, so you must make corresponding changes on the other objects if you want the relations to be bi-directional: > alpha.remove(regular) // Remove train from station, but not station from train > regular.station = null // Totally clear either a one or many relation on train > alpha.train // => [slow] > regular.station // => null All relations are persisted. There are no automatic pluralizations of collection names, even in the many case. Any prototype can define as many one and many relations as desired. add() and remove() work even when multiple many relations are defined. EPHEMERAL PROPERTIES If you have properties of a collection's objects you don't want persisted, declare them as ephemeral in the prototype script for that collection: ephemeral('conductor'); In this example from train.js, you can set the name of a conductor on the train that is valid for the current process. But once the process is restarted (or save() or reset() is called), the conductor property will be null. An example real use for this would be an HTTP connection object, which is transient, may not be persistable without error, and won't be useful after you restart the process. CALCULABLE PROPERTIES If you have a situation where a property can be derived from other properties in an object, but you might also set the property directly, use the calculable() prototype script helper method. Taking the train example, let's say that there is a property of weight per car, which is the total train weight divided by the number of cars. Sometimes this might be set directly as in: > regular.wpc = 60 // tons However, for other trains the actual weight and number of cars might be known: > express.weight = 500 // tons > express.cars = 10 In this case, the weight per car can be derived by dividing the weight by the number of cars. Ideally, in either case, you want the wpc property to return the weight per car, whether it is directly set or derived: > regular.wpc // => 60 > express.wpc // => 50 To do this, create a function in the train prototype script for calculating the potentially derivable property and then assign it to a property using the calculable() prototype script helper function: function weightPerCar() { if (this.weight && this.cars) { return this.weight / this.cars; } } calculable('wpc', weightPerCar); // A settable or calculable property OBJECTS AS PROTOTYPES To use an existing object as a prototype for another object in the same collection, pass it to the creation function as the second argument: > express = o.Train('express') > express.speed = 20 > express1 = o.Train('express1', express) > express1.engineer = 'Alice' > express2 = o.Train('express2', express) > express2.engineer = 'Bob' The database persists and reconstitutes the prototypical inheritance as needed. DB FILE The database file used to persist the database object state is a simple JSON file. It uses indenting and newlines to make it easy to read, edit, diff, and store with source control systems like svn and git. WITHOUT REPL PROMPT If you do not want to start an interactive repl prompt, but want to use an odot database from a Node application, use this in your application instead of odot.repl(): var o = odot.load(); No prompt will appear, but otherwise the API is exactly the same as demonstrated for the repl interactive prompt. LIVE MAPPING You can continue to use map() to add a new collection after load() or repl(). Example: > map('car', 'car.js'); SECURITY All mapped code is assumed to be fully trusted. Do not place third party code into your prototypes without reviewing it. Database files are written to the working directory per the umask of your user and are therefore subject to access by other users according to file system permissions. OTHER APPROACHES There are other approaches to quick, in-process databases for Node you should consider if you want document-orientation rather than object-orientation and other features like append-only disk logs, including: * nStore (https://github.com/creationix/nStore) * node-dirty (https://github.com/felixge/node-dirty) FEEDBACK Welcome at node@thomassmith.com or the Node mailing list.
About
persistent, code reloading, interactive object space for node
Resources
License
Stars
Watchers
Forks
Packages 0
No packages published